diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dd789cf --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Backend +ASPNETCORE_ENVIRONMENT=Production +APP_USE_HTTPS_REDIRECTION=false + +# JWT +JWT_KEY=CHANGE_ME_TO_A_LONG_RANDOM_SECRET +JWT_ISSUER=LineGestao +JWT_AUDIENCE=LineGestao + +# Seed admin (first login) +SEED_ADMIN_EMAIL=admin@seu-dominio.com +SEED_ADMIN_PASSWORD=CHANGE_ME_TO_A_STRONG_PASSWORD + +# PostgreSQL +POSTGRES_DB=linegestao +POSTGRES_USER=linegestao_app +POSTGRES_PASSWORD=CHANGE_ME_TO_A_STRONG_PASSWORD + +# Front-end URL used by CORS +FRONTEND_PUBLIC_URL=https://seu-dominio.com + +# Domain / HTTPS (docker-compose.prod.yml with Caddy) +API_DOMAIN=api.seu-dominio.com +ACME_EMAIL=seu-email@seu-dominio.com diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..f320404 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,8 @@ +{ + email {$ACME_EMAIL} +} + +{$API_DOMAIN} { + encode zstd gzip + reverse_proxy api:8080 +} diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..5a715fe --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,122 @@ +# Deploy rápido (API + Postgres) + +## Erro: `no configuration file provided: not found` +Esse erro acontece quando o `docker compose` é executado em uma pasta sem `docker-compose.yml`. + +Este repositório inclui `docker-compose.yml` na raiz do projeto. + +## Opção 1: subir via IP (rápido) + +1. Entre na pasta da API: + +```bash +cd ~/apps/line-gestao-api +``` + +2. Crie o `.env` a partir do exemplo: + +```bash +cp .env.example .env +nano .env +``` + +3. Suba os containers: + +```bash +docker compose up -d --build +``` + +4. Verifique o status e logs: + +```bash +docker compose ps +docker compose logs -f --tail=200 +``` + +5. Teste healthcheck da API: + +```bash +curl -I http://SEU_SERVIDOR:4000/health +``` + +## Opção 2: subir já com domínio + HTTPS automático (Caddy) + +### Pré-requisitos +- O DNS do domínio/subdomínio da API deve apontar para o IP do servidor (registro `A`). +- Portas 80 e 443 abertas no firewall/security group. + +> Se você viu erro do tipo `client version 1.24 is too old` no Traefik, use este modo com Caddy: ele não depende da API Docker para criar rotas e evita esse problema. + +### 1) Ajuste o `.env` +Na pasta da API: + +```bash +cd ~/apps/line-gestao-api +cp .env.example .env +nano .env +``` + +Preencha obrigatoriamente: +- `API_DOMAIN` (ex.: `api.seudominio.com`) +- `ACME_EMAIL` (email para emitir certificado) +- `FRONTEND_PUBLIC_URL` (URL pública do front) +- `JWT_KEY`, `SEED_ADMIN_*`, `POSTGRES_PASSWORD` + +### 2) Suba em modo produção com Caddy + +```bash +docker compose -f docker-compose.prod.yml up -d --build +``` + +### 3) Verifique saúde e certificado + +```bash +docker compose -f docker-compose.prod.yml ps +docker compose -f docker-compose.prod.yml logs -f --tail=200 +curl -i https://SEU_API_DOMAIN/health +``` + +## Checklist de confirmação (API realmente no ar) +Considere que o deploy está concluído quando **todos** os itens abaixo estiverem OK: + +```bash +docker compose ps +curl -i http://127.0.0.1:4000/health +curl -i http://SEU_IP_PUBLICO:4000/health +``` + +Para produção com domínio: + +```bash +docker compose -f docker-compose.prod.yml ps +curl -i https://SEU_API_DOMAIN/health +``` + +Resultado esperado: +- container `linegestao-api` com status `healthy`; +- container `linegestao-db` com status `healthy`; +- endpoint `/health` respondendo `HTTP/1.1 200 OK`. + +## Atualizando um clone já existente no servidor + +```bash +cd ~/apps/line-gestao-api +git fetch --all --prune +git pull --rebase origin NOME_DA_BRANCH +``` + +Depois de atualizar, suba com o arquivo que você usa: + +```bash +# modo IP +docker compose up -d --build + +# modo domínio + HTTPS +docker compose -f docker-compose.prod.yml up -d --build +``` + +## Observações +- O Postgres **não é exposto** para fora do Docker (sem `5432:5432`). +- No modo IP, a API sobe na porta `4000` do host (`4000:8080`). +- No modo domínio, a API fica atrás do Caddy em `443` (HTTPS). +- Defina valores fortes para `JWT_KEY`, `SEED_ADMIN_PASSWORD` e `POSTGRES_PASSWORD`. diff --git a/Dockerfile b/Dockerfile index bdf7133..6b5b13b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,10 @@ RUN dotnet publish "./line-gestao-api.csproj" -c Release -o /app/publish /p:UseA FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + ENV ASPNETCORE_URLS=http://+:8080 ENV ASPNETCORE_ENVIRONMENT=Production diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..d347633 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,65 @@ +services: + caddy: + image: caddy:2.10-alpine + container_name: linegestao-caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + environment: + API_DOMAIN: ${API_DOMAIN:?API_DOMAIN is required} + ACME_EMAIL: ${ACME_EMAIL:?ACME_EMAIL is required} + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + depends_on: + api: + condition: service_healthy + + db: + image: postgres:16-alpine + container_name: linegestao-db + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-linegestao} + POSTGRES_USER: ${POSTGRES_USER:-linegestao_app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + volumes: + - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-linegestao_app} -d ${POSTGRES_DB:-linegestao}"] + interval: 10s + timeout: 5s + retries: 10 + + api: + build: + context: . + dockerfile: Dockerfile + container_name: linegestao-api + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + App__UseHttpsRedirection: ${APP_USE_HTTPS_REDIRECTION:-false} + ConnectionStrings__Default: Host=db;Port=5432;Database=${POSTGRES_DB:-linegestao};Username=${POSTGRES_USER:-linegestao_app};Password=${POSTGRES_PASSWORD} + Jwt__Key: ${JWT_KEY:?JWT_KEY is required} + Jwt__Issuer: ${JWT_ISSUER:-LineGestao} + Jwt__Audience: ${JWT_AUDIENCE:-LineGestao} + Seed__AdminEmail: ${SEED_ADMIN_EMAIL:?SEED_ADMIN_EMAIL is required} + Seed__AdminPassword: ${SEED_ADMIN_PASSWORD:?SEED_ADMIN_PASSWORD is required} + Cors__AllowedOrigins__0: ${FRONTEND_PUBLIC_URL:?FRONTEND_PUBLIC_URL is required} + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"] + interval: 20s + timeout: 5s + retries: 10 + start_period: 20s + +volumes: + pg_data: + caddy_data: + caddy_config: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e72ce42 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +services: + db: + image: postgres:16-alpine + container_name: linegestao-db + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-linegestao} + POSTGRES_USER: ${POSTGRES_USER:-linegestao_app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + volumes: + - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-linegestao_app} -d ${POSTGRES_DB:-linegestao}"] + interval: 10s + timeout: 5s + retries: 10 + + api: + build: + context: . + dockerfile: Dockerfile + container_name: linegestao-api + restart: unless-stopped + depends_on: + db: + condition: service_healthy + ports: + - "4000:8080" + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + App__UseHttpsRedirection: ${APP_USE_HTTPS_REDIRECTION:-false} + ConnectionStrings__Default: Host=db;Port=5432;Database=${POSTGRES_DB:-linegestao};Username=${POSTGRES_USER:-linegestao_app};Password=${POSTGRES_PASSWORD} + Jwt__Key: ${JWT_KEY:?JWT_KEY is required} + Jwt__Issuer: ${JWT_ISSUER:-LineGestao} + Jwt__Audience: ${JWT_AUDIENCE:-LineGestao} + Seed__AdminEmail: ${SEED_ADMIN_EMAIL:?SEED_ADMIN_EMAIL is required} + Seed__AdminPassword: ${SEED_ADMIN_PASSWORD:?SEED_ADMIN_PASSWORD is required} + Cors__AllowedOrigins__0: ${FRONTEND_PUBLIC_URL:?FRONTEND_PUBLIC_URL is required} + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"] + interval: 20s + timeout: 5s + retries: 10 + start_period: 20s + +volumes: + pg_data: