chore: deploy
This commit is contained in:
parent
8e5d29c7a3
commit
b87ab63ac2
10 changed files with 326 additions and 13 deletions
147
.github/workflows/deploy-main.yml
vendored
Normal file
147
.github/workflows/deploy-main.yml
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
name: Deploy to Hostinger
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
FRONTEND_IMAGE_NAME: ${{ github.repository }}-frontend
|
||||
BACKEND_IMAGE_NAME: ${{ github.repository }}-backend
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
cache: maven
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Run backend tests
|
||||
working-directory: backend
|
||||
run: ./mvnw -B test
|
||||
|
||||
- name: Run frontend build
|
||||
working-directory: frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata for frontend
|
||||
id: meta-frontend
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=sha
|
||||
|
||||
- name: Extract Docker metadata for backend
|
||||
id: meta-backend
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=sha
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push frontend image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./frontend
|
||||
file: ./frontend/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta-frontend.outputs.tags }}
|
||||
labels: ${{ steps.meta-frontend.outputs.labels }}
|
||||
build-args: |
|
||||
VITE_API_URL=${{ secrets.FRONTEND_VITE_API_URL }}
|
||||
|
||||
- name: Build and push backend image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./backend
|
||||
file: ./backend/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta-backend.outputs.tags }}
|
||||
labels: ${{ steps.meta-backend.outputs.labels }}
|
||||
|
||||
- name: Copy deploy files to server
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.HOSTINGER_HOST }}
|
||||
username: ${{ secrets.HOSTINGER_USER }}
|
||||
key: ${{ secrets.HOSTINGER_SSH_KEY }}
|
||||
port: ${{ secrets.HOSTINGER_PORT || 22 }}
|
||||
source: "deploy/docker-compose.prod.yml"
|
||||
target: "${{ secrets.HOSTINGER_APP_PATH }}"
|
||||
strip_components: 1
|
||||
|
||||
- name: Deploy on Hostinger
|
||||
uses: appleboy/ssh-action@v1.2.0
|
||||
env:
|
||||
FRONTEND_IMAGE: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:latest
|
||||
BACKEND_IMAGE: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:latest
|
||||
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
DB_URL: ${{ secrets.DB_URL }}
|
||||
MP_JWT_VERIFY_ISSUER: ${{ secrets.MP_JWT_VERIFY_ISSUER }}
|
||||
SMALLRYE_JWT_SIGN_KEY_LOCATION: ${{ secrets.SMALLRYE_JWT_SIGN_KEY_LOCATION }}
|
||||
MP_JWT_VERIFY_PUBLICKEY_LOCATION: ${{ secrets.MP_JWT_VERIFY_PUBLICKEY_LOCATION }}
|
||||
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
|
||||
GHCR_TOKEN: ${{ secrets.GHCR_READ_TOKEN }}
|
||||
with:
|
||||
host: ${{ secrets.HOSTINGER_HOST }}
|
||||
username: ${{ secrets.HOSTINGER_USER }}
|
||||
key: ${{ secrets.HOSTINGER_SSH_KEY }}
|
||||
port: ${{ secrets.HOSTINGER_PORT || 22 }}
|
||||
envs: FRONTEND_IMAGE,BACKEND_IMAGE,POSTGRES_USER,POSTGRES_PASSWORD,DB_URL,MP_JWT_VERIFY_ISSUER,SMALLRYE_JWT_SIGN_KEY_LOCATION,MP_JWT_VERIFY_PUBLICKEY_LOCATION,GHCR_USERNAME,GHCR_TOKEN
|
||||
script: |
|
||||
set -eu
|
||||
APP_DIR="${{ secrets.HOSTINGER_APP_PATH }}"
|
||||
mkdir -p "$APP_DIR"
|
||||
cd "$APP_DIR"
|
||||
|
||||
cat > .env.production <<EOF
|
||||
POSTGRES_USER=${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
DB_URL=${DB_URL}
|
||||
FRONTEND_IMAGE=${FRONTEND_IMAGE}
|
||||
BACKEND_IMAGE=${BACKEND_IMAGE}
|
||||
MP_JWT_VERIFY_ISSUER=${MP_JWT_VERIFY_ISSUER}
|
||||
SMALLRYE_JWT_SIGN_KEY_LOCATION=${SMALLRYE_JWT_SIGN_KEY_LOCATION}
|
||||
MP_JWT_VERIFY_PUBLICKEY_LOCATION=${MP_JWT_VERIFY_PUBLICKEY_LOCATION}
|
||||
EOF
|
||||
|
||||
echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
|
||||
docker compose --env-file .env.production -f docker-compose.prod.yml pull
|
||||
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
|
||||
docker image prune -f
|
||||
32
GITHUB_SECRETS_CHECKLIST.md
Normal file
32
GITHUB_SECRETS_CHECKLIST.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# GitHub Secrets Checklist
|
||||
|
||||
Use esta lista para acompanhar quais `GitHub Secrets` do deploy ja foram cadastrados no repositório.
|
||||
|
||||
## Acesso ao servidor
|
||||
|
||||
- [x] `HOSTINGER_HOST`
|
||||
- [x] `HOSTINGER_PORT`
|
||||
- [x] `HOSTINGER_USER`
|
||||
- [x] `HOSTINGER_SSH_KEY`
|
||||
- [x] `HOSTINGER_APP_PATH`
|
||||
|
||||
## Registro de imagens
|
||||
|
||||
- [x] `GHCR_USERNAME`
|
||||
- [x] `GHCR_READ_TOKEN`
|
||||
|
||||
## Frontend
|
||||
|
||||
- [x] `FRONTEND_VITE_API_URL`
|
||||
|
||||
## Banco de dados
|
||||
|
||||
- [x] `POSTGRES_USER`
|
||||
- [x] `POSTGRES_PASSWORD`
|
||||
- [x] `DB_URL`
|
||||
|
||||
## Backend / JWT
|
||||
|
||||
- [ ] `MP_JWT_VERIFY_ISSUER`
|
||||
- [ ] `SMALLRYE_JWT_SIGN_KEY_LOCATION`
|
||||
- [ ] `MP_JWT_VERIFY_PUBLICKEY_LOCATION`
|
||||
50
README.md
50
README.md
|
|
@ -105,3 +105,53 @@ Frontend:
|
|||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Deploy automatizado
|
||||
|
||||
O repositório agora inclui um workflow em `.github/workflows/deploy-main.yml` para deploy automático quando houver push na branch `main`.
|
||||
|
||||
Fluxo:
|
||||
|
||||
- roda testes do backend
|
||||
- roda build do frontend
|
||||
- gera e publica imagens Docker no GHCR
|
||||
- copia `deploy/docker-compose.prod.yml` para o servidor
|
||||
- conecta via SSH na Hostinger e executa `docker compose pull && docker compose up -d`
|
||||
|
||||
Arquivos de apoio:
|
||||
|
||||
- `backend/Dockerfile`: imagem de produção do backend
|
||||
- `frontend/Dockerfile`: build do Vite + entrega por Nginx
|
||||
- `frontend/nginx.conf`: fallback SPA
|
||||
- `deploy/docker-compose.prod.yml`: stack de produção
|
||||
- `deploy/.env.production.example`: variáveis esperadas no servidor
|
||||
|
||||
Secrets necessários no GitHub:
|
||||
|
||||
- `HOSTINGER_HOST`: IP ou domínio do servidor
|
||||
- `HOSTINGER_PORT`: porta SSH, normalmente `22`
|
||||
- `HOSTINGER_USER`: usuário SSH
|
||||
- `HOSTINGER_SSH_KEY`: chave privada SSH usada no acesso ao servidor
|
||||
- `HOSTINGER_APP_PATH`: diretório remoto onde o deploy será mantido, por exemplo `/home/user/apps/beetask`
|
||||
- `GHCR_USERNAME`: usuário dono do pacote no GHCR
|
||||
- `GHCR_READ_TOKEN`: token com permissão de `read:packages` para o servidor fazer pull das imagens
|
||||
- `FRONTEND_VITE_API_URL`: URL que o frontend usará para a API, por exemplo `https://api.seudominio.com` ou `/api`
|
||||
- `POSTGRES_USER`
|
||||
- `POSTGRES_PASSWORD`
|
||||
- `DB_URL`: JDBC URL completa do banco, por exemplo `jdbc:postgresql://203.0.113.10:5432/beetask`
|
||||
- `MP_JWT_VERIFY_ISSUER`
|
||||
- `SMALLRYE_JWT_SIGN_KEY_LOCATION`
|
||||
- `MP_JWT_VERIFY_PUBLICKEY_LOCATION`
|
||||
|
||||
Pré-requisitos no servidor:
|
||||
|
||||
- Docker instalado
|
||||
- Docker Compose plugin disponível via `docker compose`
|
||||
- pasta definida em `HOSTINGER_APP_PATH` com permissão de escrita para o usuário SSH
|
||||
- caso as imagens GHCR sejam privadas, o `GHCR_READ_TOKEN` precisa conseguir fazer pull
|
||||
|
||||
Observações:
|
||||
|
||||
- o workflow usa a tag `latest` para o deploy em produção
|
||||
- a rota do frontend no container está exposta na porta `3000` do servidor e o backend na `8080`; normalmente você vai colocar Nginx, Caddy ou o proxy da Hostinger na frente dessas portas
|
||||
- se quiser servir frontend e backend em domínios diferentes, ajuste `FRONTEND_VITE_API_URL`
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
# Database
|
||||
DB_USER=beetask
|
||||
DB_PASSWORD=beetask
|
||||
DB_URL=jdbc:postgresql://localhost:5432/beetask
|
||||
|
||||
# Optional overrides
|
||||
# QUARKUS_HTTP_PORT=8080
|
||||
# QUARKUS_HTTP_CORS_ORIGINS=http://localhost:5173
|
||||
# MP_JWT_VERIFY_ISSUER=https://beetech.net
|
||||
# SMALLRYE_JWT_SIGN_KEY_LOCATION=privateKey.pem
|
||||
# MP_JWT_VERIFY_PUBLICKEY_LOCATION=publicKey.pem
|
||||
DB_URL=jdbc:postgresql://localhost:5432/beetask
|
||||
28
backend/Dockerfile
Normal file
28
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
FROM maven:3.9.11-eclipse-temurin-21 AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pom.xml mvnw ./
|
||||
COPY .mvn .mvn
|
||||
|
||||
RUN chmod +x mvnw
|
||||
RUN ./mvnw -B dependency:go-offline
|
||||
|
||||
COPY src src
|
||||
|
||||
RUN ./mvnw -B clean package -DskipTests
|
||||
|
||||
FROM eclipse-temurin:21-jre
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/target/quarkus-app/lib/ ./lib/
|
||||
COPY --from=build /app/target/quarkus-app/*.jar ./
|
||||
COPY --from=build /app/target/quarkus-app/app/ ./app/
|
||||
COPY --from=build /app/target/quarkus-app/quarkus/ ./quarkus/
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "/app/quarkus-run.jar"]
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
quarkus.http.port=8080
|
||||
quarkus.http.cors=true
|
||||
quarkus.http.cors.origins=http://localhost:5173
|
||||
|
||||
quarkus.http.cors.origins=*
|
||||
quarkus.datasource.db-kind=postgresql
|
||||
quarkus.datasource.username=${DB_USER:beetask}
|
||||
quarkus.datasource.password=${DB_PASSWORD:beetask}
|
||||
|
|
@ -17,6 +16,6 @@ quarkus.swagger-ui.always-include=true
|
|||
quarkus.smallrye-health.root-path=/status
|
||||
|
||||
# JWT Security
|
||||
mp.jwt.verify.issuer=https://beetech.net
|
||||
smallrye.jwt.sign.key.location=privateKey.pem
|
||||
mp.jwt.verify.publickey.location=publicKey.pem
|
||||
mp.jwt.verify.issuer=${MP_JWT_VERIFY_ISSUER}
|
||||
smallrye.jwt.sign.key.location=${SMALLRYE_JWT_SIGN_KEY_LOCATION}
|
||||
mp.jwt.verify.publickey.location=${MP_JWT_VERIFY_PUBLICKEY_LOCATION}
|
||||
|
|
|
|||
11
deploy/.env.production.example
Normal file
11
deploy/.env.production.example
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
POSTGRES_USER=beetask
|
||||
POSTGRES_PASSWORD=change-me
|
||||
DB_URL=jdbc:postgresql://203.0.113.10:5432/beetask
|
||||
|
||||
BACKEND_IMAGE=ghcr.io/owner/repo-backend:latest
|
||||
FRONTEND_IMAGE=ghcr.io/owner/repo-frontend:latest
|
||||
|
||||
QUARKUS_HTTP_CORS_ORIGINS=https://app.example.com
|
||||
MP_JWT_VERIFY_ISSUER=https://beetech.net
|
||||
SMALLRYE_JWT_SIGN_KEY_LOCATION=privateKey.pem
|
||||
MP_JWT_VERIFY_PUBLICKEY_LOCATION=publicKey.pem
|
||||
22
deploy/docker-compose.prod.yml
Normal file
22
deploy/docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
services:
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DB_USER: ${POSTGRES_USER}
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_URL: ${DB_URL}
|
||||
QUARKUS_HTTP_PORT: 8080
|
||||
MP_JWT_VERIFY_ISSUER: ${MP_JWT_VERIFY_ISSUER}
|
||||
SMALLRYE_JWT_SIGN_KEY_LOCATION: ${SMALLRYE_JWT_SIGN_KEY_LOCATION}
|
||||
MP_JWT_VERIFY_PUBLICKEY_LOCATION: ${MP_JWT_VERIFY_PUBLICKEY_LOCATION}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "3000:80"
|
||||
20
frontend/Dockerfile
Normal file
20
frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG VITE_API_URL=/api
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.29-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
11
frontend/nginx.conf
Normal file
11
frontend/nginx.conf
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue