Deploy — Rede Cívica
Este documento descreve onde e como o MVP da Rede Cívica roda: a infraestrutura, a pipeline de CI/CD, os componentes em execução, o armazenamento, a configuração e a operação.
Nenhum valor real de ambiente entra neste repositório. Senhas, chaves, tokens, endereços internos e identificadores de recursos vivem nos secrets do Coolify e do GitHub. Os templates públicos estão nos repositórios de código.
Estado atual: o ambiente dev está no ar em dev.redecivica.com.br e o site de documentação em docs.redecivica.com.br. O ambiente de produção entra depois da validação de dev.
Visão geral
Seção intitulada “Visão geral”O código vive na org Rede-Civica do GitHub. O CI roda no GitHub Actions, publica imagens no GHCR e a VPS puxa essas imagens pelo Coolify. A VPS é uma HostGator NVMe 4, com 2 vCPU, 4 GB de RAM e 100 GB de NVMe, em São Paulo.
GitHub (org Rede-Civica) mvp-api · mvp-web │ push em develop ou main ▼GitHub Actions (lint, typecheck, test, build) │ docker push ▼GHCR (ghcr.io/rede-civica/mvp-api, ghcr.io/rede-civica/mvp-web) │ docker pull ▼VPS HostGator (São Paulo) · Coolify + Traefik Traefik :443 (TLS automático) └── web (nginx) ── /api/ ──> api (NestJS) ──> postgres ── /tiles/ ─> tiles (martin) ──> brasil.mbtiles (disco) api ──> OCI Object Storage (mídia e backups em buckets separados)Três decisões estruturam o desenho:
- Mesma origem. Traefik serve o front e a API no mesmo domínio. O nginx do web faz proxy de
/api/,/tiles/,/d6a/,/d6b/e/admin/moderacao/. O navegador conversa com uma origem só. - Infraestrutura no Brasil. A VPS e o object storage ficam em São Paulo. Os processadores estrangeiros restantes são o Google (login) e a Esri (camada de satélite).
- VPS única. Web, API, tiles e banco dividem a mesma máquina. É um ponto único de falha aceito na escala piloto.
Onde cada peça roda
Seção intitulada “Onde cada peça roda”| Peça | Onde | Tecnologia |
|---|---|---|
| Front-end | VPS, container web |
nginx servindo o bundle estático do Vite |
| API | VPS, container api |
NestJS em Node 22 |
| Assistência de texto | VPS, container languagetool |
LanguageTool 6.8 self-hosted, pt-BR |
| Tiles | VPS, container tiles |
martin 1.14.0 servindo brasil.mbtiles |
| Banco | VPS, container postgres |
PostgreSQL 16 |
| Site de documentação | VPS, container docs |
nginx servindo o site estático gerado pelo Astro Starlight |
| Orquestração | VPS | Coolify com proxy Traefik |
| Imagens | GHCR | ghcr.io/rede-civica/mvp-api, mvp-web e mvp-docs |
| Mídia e backups | OCI Object Storage | API S3-compatible, região sa-saopaulo-1 |
| DNS | Registro.br | zona redecivica.com.br |
Repositórios e imagens
Seção intitulada “Repositórios e imagens”Os repos de código da org Rede-Civica são mvp-api (back-end), mvp-web (front-end) e docs (este repositório). O CI dos dois repos de código publica imagens no GHCR com o nome do repositório. Cada imagem carrega a tag sha do commit e uma tag por branch:
| Branch | Tags publicadas | Ambiente |
|---|---|---|
develop |
sha + dev |
dev |
main |
sha + latest |
produção |
| pull request | nenhuma (só valida o build) | — |
A tag sha é imutável e permite voltar a uma versão exata. As tags dev e latest andam a cada push.
Ambientes
Seção intitulada “Ambientes”Dois ambientes, montados um de cada vez. O dev subiu primeiro; a produção entra depois da validação.
| Ambiente | Branch | Tag | Domínio | Estado |
|---|---|---|---|---|
| dev | develop |
dev |
dev.redecivica.com.br |
no ar |
| produção | main |
latest |
app.redecivica.com.br |
depois da validação de dev |
Cada ambiente tem o próprio conjunto de secrets, o próprio client OAuth, o próprio bucket e o próprio domínio. Nada é compartilhado entre os dois.
Pipeline de CI/CD
Seção intitulada “Pipeline de CI/CD”Cada repo tem um workflow build-image.yml disparado em push para develop ou main e em pull request. Os jobs:
lint: ESLint. No web, tambémformat:check.typecheck:tsc. Na API, comprisma generateantes.test: Jest na API e Vitest no web, com thresholds de cobertura.imagem: build sem push, apenas em pull request.imagem-deveimagem-prod: build e push no GHCR, em push paradevelopoumain.
Os jobs de imagem dependem dos três gates. Gate vermelho bloqueia a publicação.
O build da imagem do web recebe as variáveis do client como build args, vindos dos secrets do repositório: VITE_GOOGLE_CLIENT_ID, um por ambiente. A imagem da API não tem segredo de build. Os valores de runtime chegam pelos secrets do Coolify.
Não existe trigger automático de deploy no fim do workflow. Depois que o CI publica a tag nova, o operador dispara o redeploy no painel do Coolify. A automação por webhook é passo planejado; quando entrar, a URL do webhook vive como secret do repositório, nunca no código.
Componentes em execução
Seção intitulada “Componentes em execução”Coolify e Traefik
Seção intitulada “Coolify e Traefik”O Coolify gerencia os containers, os volumes persistentes, os backups e os domínios. Ele roda na VPS com instâncias próprias de PostgreSQL e Redis para uso interno. O Traefik é o proxy reverso: escuta em 80 e 443, emite e renova o certificado TLS automaticamente (ACME HTTP-01) e roteia por label de container. Só os serviços com domínio configurado recebem rota.
web (nginx)
Seção intitulada “web (nginx)”Imagem multi-stage: node:22-alpine compila o bundle e nginx:alpine serve em porta interna 80. A configuração do nginx cobre:
- Fallback de SPA (
try_filespara oindex.html). - Cache imutável para
/assets/com hash no nome, sem cache parasw.js,registerSW.jseindex.html. .mjsservido comotext/javascript, exigido pelo worker do MapLibre.- gzip para texto, JSON e wasm.
- Proxy de
/api/,/d6a/,/d6b/e/admin/moderacao/para o containerapie de/tiles/para o containertiles, com o prefixo/tilesremovido antes do encaminhamento.
A resolução DNS dos upstreams usa o resolver do Docker, então o nginx sobe antes dos containers de origem.
api (NestJS)
Seção intitulada “api (NestJS)”Imagem multi-stage em node:22-slim (o onnxruntime exige glibc). No boot, o container aplica as migrations do Prisma com prisma migrate deploy e sobe o servidor. O health check do Docker chama GET /api/observability/health a cada 30s.
A configuração de runtime vem inteira de variáveis de ambiente registradas como secrets no Coolify. A lista completa, com placeholders, está em .env.dev.example e .env.production.example no repo mvp-api. Os nomes MINIO_* são mantidos no código por compatibilidade; os valores apontam para o bucket do OCI.
Os modelos de inferência ficam em um volume nomeado montado em /app/modelos-d1b: Whisper tiny e Florence-2 base na D-1b, classificador NSFW e detector de pessoa na D-1c. Os pesos são baixados do HuggingFace no primeiro uso de cada pipeline, cerca de 1,3 GB no total. O classificador NSFW ocupa cerca de 86 MB em disco e 100 MB residentes; o detector de pessoa, cerca de 42 MB em disco e 50 MB residentes. O volume sobrevive aos redeploys.
Os limites de recurso no painel são 2,5 GiB de memória e 1,5 vCPU. A VPS tem 4 GB de RAM e um swap de 2 GB. A conta de memória inclui API, modelos, PostgreSQL, Coolify/Traefik e martin. O classificador NSFW e o detector de pessoa usam dtype quantizado para conter a RAM. Com os três modelos carregados, o pico estimado fica em cerca de 1,7 GB, dentro do limite.
O docker-compose.vps-sim.yml reproduz essa conta no desenvolvimento: a API roda com mem_limit de 2,5 GiB e 1,5 vCPU e monta o mesmo volume de modelos. Se o OOM aparecer no sim, a válvula é D1B_DTYPE_MODELOS=fp16 ou desligar a detecção de pessoa com D1C_PESSOA_HABILITADA=false.
languagetool (LanguageTool)
Seção intitulada “languagetool (LanguageTool)”Motor de revisão de português da assistência de texto (POST /api/assistencia/texto), self-hosted. Roda a imagem comunitária erikvl87/languagetool:6.8, com heap de 512 MB e limite de 768 MB, sem n-gramas e sem porta pública. A API o alcança pelo alias interno languagetool:8010, configurado em ASSISTENCIA_URL. O serviço é opcional para o restante do sistema: com ele fora do ar, a assistência responde disponivel: false e o envio do formulário segue normal. O compose versionado está em deploy/coolify/languagetool.yml do repo mvp-api.
postgres
Seção intitulada “postgres”PostgreSQL 16 criado pelo one-click do Coolify, com volume persistente próprio e senha forte gerada pelo painel. Não expõe porta pública; só a rede interna do Docker o alcança. A API conecta pelo hostname interno via DATABASE_URL.
tiles (martin)
Seção intitulada “tiles (martin)”O arquivo brasil.mbtiles, de cerca de 24,3 GiB, é gerado pelos scripts em scripts/tiles/ do repo mvp-api a partir do extrato do OpenStreetMap (Geofabrik), dos footprints da Microsoft GlobalMLBuildingFootprints (CDLA-Permissive-2.0) e do tema base.land_cover da Overture Maps (ODbL, derivado do ESA WorldCover), combinados na geração. O Brasil ocupa o z0–16 com os três grupos de dado. Os vizinhos da América do Sul ocupam o mesmo z0–16, gerados do extrato south-america-latest.osm.pbf recortado para excluir o polígono do Brasil, sem prédios e sem verde da Overture. A fusão dos tiles de fronteira decodifica os MVT e une as camadas por (camada, id), com a feição do Brasil prevalecendo. O z0–7 permanece como veio da cobertura de contexto anterior. O arquivo fica em um diretório da VPS e é montado somente leitura no container. O serviço roda martin 1.14.0 com o arquivo como argumento e responde o tileset brasil, esquema OpenMapTiles, z0 a 16. O app usa overzoom acima do maxzoom do tileset, até o zoom 19.
O MBTiles não vai para o object storage. As reimportações repetem o mesmo fluxo: gerar localmente, fundir os tiles dos vizinhos e copiar o arquivo para o diretório da VPS.
A camada base é derivada do OpenStreetMap e disponibilizada sob a ODbL, com a atribuição “© OpenStreetMap contributors” visível no app. O esquema de camadas é do OpenMapTiles (CC-BY), os prédios combinam o OpenStreetMap com a Microsoft Building Footprints (CDLA-Permissive-2.0), com a atribuição “Microsoft” visível no app, e a cobertura vegetal vem do tema base.land_cover da Overture Maps (ODbL), derivado do ESA WorldCover, com as atribuições “Overture Maps Foundation” e “ESA WorldCover / Copernicus” visíveis no app.
O guia mapa_e_tiles.md apresenta os conceitos, os formatos de dado e a cadeia completa, do extrato ao render.
Rede interna
Seção intitulada “Rede interna”O Coolify coloca os serviços na rede Docker coolify com os aliases api, tiles e languagetool. O nginx do web e a API usam esses aliases. O nome do container muda a cada deploy e nunca é usado para comunicação. Nos recursos declarados como Docker Compose, o alias é explícito no arquivo de deploy/coolify/ do repo mvp-api.
Armazenamento e backups
Seção intitulada “Armazenamento e backups”Mídia e backups do banco vivem no OCI Object Storage, S3-compatible, região de São Paulo. Cada ambiente tem buckets separados por uso: um para mídia e outro para backups.
- Mídia: o navegador envia direto para o bucket por URL presigned gerada pela API. A API também lê a mídia pelo mesmo gateway para processar áudio e imagem. O bucket de mídia não guarda backup; o MBTiles fica fora.
- Backups: o Coolify roda um dump diário do PostgreSQL (cron
0 2 * * *), mantém 4 cópias com teto de 10 GB e envia para o bucket dedicado de backups. O backup local fica desativado. O free tier de 20 GB cobre a escala piloto. - Separação de buckets: o cliente S3 da API é preso a um bucket, definido por
MINIO_BUCKET. Com os dumps em bucket separado, uma falha de assinatura na leitura de mídia não alcança os backups. Dev usarede-civica-dev(mídia) erede-civica-dev-backups(backups). Produção repete comrede-civicaerede-civica-backups. - CORS: o gateway S3-compatible do OCI responde
Access-Control-Allow-Origin: *por conta própria e não aceita regra por bucket nesta versão. O bucket é privado e o acesso depende de URL presigned. O MVP aceita o CORS aberto e registra a limitação. Se a restrição for exigida antes da produção, entra por proxy de apresentação (reaproveitando oMINIO_PUBLIC_ENDPOINT) ou media proxy na API.
Residência de dados: a VPS e o bucket ficam no Brasil. HostGator e Oracle entram na lista de processadores da política de privacidade. Os processadores externos restantes são o Google (login) e a Esri (camada de satélite).
DNS e TLS
Seção intitulada “DNS e TLS”A zona redecivica.com.br é gerenciada no Registro.br. Cada ambiente tem um registro A apontando para o IP da VPS. O Traefik emite o certificado TLS automaticamente quando o recurso web com o domínio sobe. O HTTP redireciona para HTTPS.
O site de documentação tem registro A próprio de docs.redecivica.com.br para o mesmo IP, com o TLS emitido pelo mesmo Traefik. Ele é um container nginx separado, fora do domínio do app.
O mesmo domínio serve o app e a API. O nginx do web faz proxy de /api/, /tiles/, /d6a/, /d6b/ e /admin/moderacao/, então o navegador fala com uma origem só.
Configuração e segredos
Seção intitulada “Configuração e segredos”Nenhum valor real de ambiente é versionado. O repositório público carrega só templates:
mvp-api/.env.dev.exampleemvp-api/.env.production.example: lista completa de variáveis com placeholders.mvp-web/.env.example: variáveis do client, todasVITE_.
Os valores reais vivem em:
- Secrets do Coolify: variáveis da API por ambiente (banco, JWT, OAuth, papéis de operação, storage, retenção).
- Secrets do repositório no GitHub: build args do web e a futura URL de webhook.
- Token de leitura de pacotes: o Coolify autentica no GHCR com um token de escopo
read:packages, com expiração e rotação.
A imagem do web não carrega segredo de runtime. As variáveis VITE_ entram no bundle em tempo de build e são públicas por natureza. O client ID do Google é público; a camada base do mapa é servida por infraestrutura própria e o OpenStreetMap raster fica apenas como fallback de erro.
Operação
Seção intitulada “Operação”- Health: a API expõe liveness em
/api/observability/healthe readiness em/api/observability/health/readiness, que checa banco, DLQ e storage. O container declara health check no Docker. Os outros serviços têm checagem própria no Coolify. - Rollback: redeploy de uma tag
shaanterior no painel do Coolify. A tag é imutável, então a versão retornada é exata. - Restauração de backup: o drill de restauração do dump do bucket é rotina operacional planejada.
- Logs: o driver do Docker rotaciona os logs como medida interina. O log estruturado em arquivo é evolução pendente.
- Warm-up dos modelos: no primeiro uso de cada pipeline a API baixa os pesos para o volume. Aquecer antes de abrir tráfego é recomendado em ambiente novo.
- Limites de recurso: definidos por serviço no painel do Coolify e aplicados a cada recreate.
- Acesso ao host: SSH só por chave, com login de root por senha desabilitado, firewall restrito às portas necessárias e fail2ban ativo.
- Instância única: locks e caches são em memória no MVP. A API roda em uma réplica. Escala horizontal exige Redis e fica para a Fase 2.
Fluxo de deploy
Seção intitulada “Fluxo de deploy”Os recursos do Coolify declarados como Docker Compose têm o arquivo versionado em deploy/coolify/ do repo mvp-api. O painel não lê o repositório: a alteração vale depois de colar o arquivo no recurso e redeployar.
Rotina, depois que o ambiente existe:
- Abrir pull request para
developno repo com a mudança. O CI roda os gates e valida o build da imagem. - Fazer o merge. O CI roda os gates de novo, publica a tag
deve oshado commit, aciona o deploy no Coolify e valida a saúde no domínio. - Mudança em recurso Docker Compose (
tileselanguagetool): editar o arquivo emdeploy/coolify/, colar no painel do Coolify, salvar e redeployar o recurso. - Validar no domínio: SPA respondendo,
/api/observability/healthcom 200 e/tiles/brasilcom o JSON do tileset. - Para produção, repetir o fluxo em
main, que publicalatest, depois de validar o dev.
O provisionamento inicial de um ambiente, já feito para o dev, segue esta ordem:
- Projeto no Coolify com um environment.
- PostgreSQL one-click, com destino de backup no bucket.
- Recurso da API a partir da imagem, com os secrets do ambiente e o volume de modelos.
- Recurso do LanguageTool via Docker Compose, colando
deploy/coolify/languagetool.ymldo repomvp-api. - Recurso dos tiles via Docker Compose, colando
deploy/coolify/tiles.yml, com o diretório do MBTiles montado. - Recurso do web com o domínio e o TLS automático.
- Registro A no DNS antes de subir o recurso do web.
- Smoke test fim a fim: demanda com foto e áudio, login Google e tiles.
Site de documentação
Seção intitulada “Site de documentação”O site tem o próprio repositório e o próprio ciclo, sem passar pelos recursos do app:
- Merge ou push no
masterdo repodocs. - O workflow
notificar-site.ymldodocsdispara o build do site porrepository_dispatch. - O workflow do site roda os gates, clona o
docs, gera as páginas e publica a imagem no GHCR com as tagsshaelatest. - O webhook do Coolify aciona o redeploy e a saúde é verificada em
https://docs.redecivica.com.br/e em uma página de conteúdo. - Validar no domínio: home, uma página de SDS, o guia visual e a busca.
O rollback troca a imagem do recurso para a tag sha anterior no painel e volta para latest quando a correção entrar.
Segurança
Seção intitulada “Segurança”- O nginx do web serve cabeçalhos de segurança em todas as respostas (HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy e CSP), com
server_tokens off. A CSP libera a própria origem, o domínio de apresentação do bucket (connect-srcpara o upload presigned,img-srcemedia-srcpara a mídia lida pela D-7), o host cru do gateway OCI, a camada de satélite da Esri, os fallbacks de mapa e o script do Google usado no login. O domínio de apresentação de produção entra na mesma lista quando o bucket de produção for criado. - A API aplica CORS por allowlist (
CORS_ORIGINS). Em produção, lista vazia significa nenhuma origem externa. - O rate limit é por IP do cliente. O nginx normaliza a cadeia
X-Forwarded-Forantes de repassar, o que fecha o vetor de spoofing comTRUST_PROXY=1. - As rotas de operação exigem papel ou IP de operação.
metricsereadinessficam restritos em produção, e o liveness segue público para o health check do container. Eventos e rebuilds exigem papel de operador. - A identidade anônima usa prova de posse do dispositivo (
X-Device-Segredo) no vínculo com a conta Google. O login sem o segredo não transfere os dados do aparelho. - A fila de moderação fica atrás de JWT com papel de moderação, e o nginx faz proxy de
/admin/moderacao/.
Limitações conhecidas
Seção intitulada “Limitações conhecidas”- O deploy da API e do web é disparado ao fim do CI por webhook do Coolify. Os recursos Docker Compose (
tileselanguagetool) continuam com deploy manual no painel, a partir do arquivo dedeploy/coolify/. - A rotação de logs pelo driver do Docker é interina. O log estruturado está pendente.
- A imagem da API tem cerca de 2,17 GB. Excluir dependências de desenvolvimento no estágio de produção corta perto de 1 GB.
- O CORS do gateway do OCI responde
*e não aceita regra por bucket. O MVP aceita e documenta. Restringir a origem exigiria proxy de apresentação ou media proxy. - O drill de restauração do banco está pendente.
- A API roda em instância única. Locks e caches são em memória.
- Front, API, tiles e banco dividem uma VPS. Uma queda derruba todos juntos. CDN na frente dos tiles e plano de resiliência do front são passos futuros.
Referências
Seção intitulada “Referências”- Repo mvp-api, arquivo README.md: setup, variáveis e operação da API.
- Repo mvp-web, arquivo README.md: setup, build e nginx do front.
- .env.dev.example e .env.production.example: templates de ambiente.
- scripts/tiles/ no repo
mvp-api: geração dobrasil.mbtiles. - deploy/coolify/ no repo
mvp-api: composes versionados dos recursos Docker Compose do Coolify e o passo a passo no painel. - sds/N-0c - Observabilidade.md: endpoints de health e métricas.