Sobe ambientes temporários isolados a partir de qualquer branch/commit do GitLab, para o setor de Qualidade testar sem depender de Dev ou Infra.
Cada ambiente recebe container próprio, banco próprio (restaurado de backup ou copiado de produção/homologação), URL única com HTTPS (DNS no Pi-hole + Traefik), prazo de validade com renovação, remoção automática e auditoria completa.
Faz parte da família Capiva* da CapivApp e herda a
identidade da marca (mascote, índigo #6366F1 → magenta #EC4899).
Documentação detalhada em /doc · progresso em TODO.md.
bun install
bun run infra:up # Postgres + Traefik + Pi-hole (docker compose)
bun run prisma:generate
bun run seed # cria o admin (ADMIN_EMAIL / ADMIN_PASSWORD)
bun run dev:all # API (3000) + worker + web (5173)Processos separados, se preferir: bun run dev (API), bun run worker (fila e
crons), bun run dev:web (frontend).
- Projeto → branch → commit (navegação pela API do GitLab, com autor, data e mensagem do commit).
- Dockerfile e porta do build (repositórios com vários Dockerfiles).
- Origem do banco: upload
.sql/.sql.gz, backup salvo, cópia de produção ou cópia de homologação. - Dependências: as variáveis liberadas pelo admin. As do tipo
urltrazem um seletor dos ambientes já no ar — o QA aponta o front para o back sem digitar endereço (ver abaixo). - Deploy — o pipeline provisiona tudo e publica a URL.
Um front precisa saber a URL do back. Para isso:
- O admin cadastra a variável no projeto (ex.:
VITE_API_BASE_URL) marcando o tipo URL (dependência) e, se quiser, um valor padrão e a obrigatoriedade. - O QA, ao criar o ambiente, vê essa variável na seção Dependências e escolhe num select o ambiente de destino: a URL é preenchida sozinha. Digitar à mão continua possível.
- Variáveis cujo nome sugere endereço (
*_URL,*_URI,*_ENDPOINT,*_HOST,API_BASE*…) ganham o seletor mesmo sem o tipo declarado. - Depois de criado, o botão Dependências no card do ambiente permite corrigir as URLs e refazer o deploy num passo só.
Obrigatórias em branco e URLs mal formadas bloqueiam o deploy no formulário, com o motivo ao lado do campo.
Cada variável do projeto declara quando vale — no cadastro e na edição:
| Escopo | Vai para | Use quando |
|---|---|---|
| Build + runtime (padrão) | --build-arg e env do container |
quase tudo |
| Só no build | --build-arg |
a imagem precisa em tempo de build (migrations, bundle) |
| Só no runtime | env do container | só o processo em execução usa |
A variável do banco é tratada automaticamente, e essa é a razão de o recurso
existir: quando o projeto define um usuário de aplicação (RLS), o build recebe
a URL do usuário administrativo — dono das tabelas, exigido pelo
prisma migrate deploy — e o container recebe a do usuário de aplicação,
sujeito a RLS. Com um mapa único de variáveis, o build herdava a URL do usuário
de aplicação e a migration quebrava com must be owner of table. É a mesma
separação que os Dockerfiles fazem entre MIGRATION_DATABASE_URL e
DATABASE_URL.
| Ação | O que faz |
|---|---|
| Reiniciar | Reinicia só o container. Banco, imagem, rede e DNS intactos. |
| Refazer deploy | Roda o pipeline do zero — recria o banco a partir da origem. Pede confirmação. |
| Renovar | Estende a validade, respeitando prazo máximo e limite de renovações. |
| Dependências | Edita as variáveis autorizadas; opcionalmente já refaz o deploy. |
| Progresso/Logs/Console | SSE do pipeline, logs do container e shell interativo (xterm). |
| Excluir | Remove container, banco, volume, rede e registro DNS. Pede confirmação. |
Reiniciar e refazer deploy eram a mesma operação destrutiva antes; hoje são separadas, para o QA não perder os dados de teste ao reiniciar a aplicação.
Os ambientes não precisam rodar na mesma máquina do CapivaDeploy. Em Configurações → Servidores de deploy (ADMIN) cadastra-se cada alvo:
| Campo | Para que serve |
|---|---|
| Runtime | Docker ou Podman — ambos falam a mesma Engine API. |
| Conexão | Local (socket do próprio host) ou Remoto (SSH). |
| IP / porta / usuário | Endereço do servidor remoto. |
| Chave SSH privada | Chave sem passphrase com acesso ao engine. Guardada no banco e nunca devolvida pela API. |
| Socket | Opcional. Vazio = padrão do runtime (Docker /var/run/docker.sock, Podman /run/podman/podman.sock). |
| Padrão | Usado por projetos que não escolheram servidor. |
O botão Testar faz o handshake real com o engine (pede a versão) — é o jeito de validar IP, chave e socket antes do primeiro deploy.
No cadastro do projeto, o campo Servidor de deploy escolhe o alvo. O servidor é congelado no ambiente no momento da criação: logs, console, reinício e remoção continuam mirando o engine onde ele subiu, mesmo que o projeto passe a apontar para outro servidor depois.
- Docker e Podman compartilham o mesmo adapter (
DockerService): o Podman expõe uma Engine API compatível, então não há um segundo cliente. - Para SSH, a Engine API trafega dentro da sessão SSH e é exposta como um
socket unix local (
@modules/container/sshSocketBridge) — do ponto de vista do dockerode não há diferença para o caso local. - Duas razões para a ponte, em vez de um
http.Agentcustomizado (o caminho que o próprio dockerode usa):- O Bun não invoca
agent.createConnection; o Node invoca. Como a API e o worker rodam em Bun, um agente customizado é silenciosamente ignorado e o cliente acaba fazendo HTTP puro contra a porta 22. - O transporte SSH do dockerode fixa
docker system dial-stdio; na ponte o comando é parâmetro, então Podman remoto funciona sem o shimpodman-dockerno servidor.
- O Bun não invoca
- A ponte bufferiza o início da requisição: o canal SSH só abre depois do
handshake (~1s), e o Bun descarta o que chega num socket antes de existir
um leitor (o Node bufferiza). Sem isso a requisição se perde e a conexão fica
pendurada. As duas peculiaridades do Bun estão fixadas em
sshSocketBridge.test.ts— se algum dia mudarem, os testes quebram e a ponte pode ser simplificada. - A seleção "qual cliente para qual servidor" vive numa única Factory
(
ContainerOrchestratorFactory) + strategy map por tipo de conexão; steps, workers e rotas só pedem o alvo.
# Docker: o usuário SSH precisa estar no grupo docker
sudo usermod -aG docker deploy
# Podman: habilitar o socket do serviço
sudo systemctl enable --now podman.socket
# rootless: /run/user/<uid>/podman/podman.sock — informe em "Socket" no cadastroPostgres efêmero continua central. Os bancos dos ambientes seguem sendo criados no servidor Postgres do CapivaDeploy (
EPHEMERAL_PG_*) — o servidor de deploy precisa alcançá-lo pela rede. Isso mantémpg_dump/psqle os backups num lugar só; o que passa a ser remoto é o build e a execução do container.
| Papel | Pode |
|---|---|
| Administrador | Tudo: projetos, variáveis, prazos, configurações, backups, qualquer ambiente. |
| QA | Criar, renovar, reiniciar, editar dependências e excluir os ambientes próprios. |
| Visualizador | Somente leitura. |
Configurações e Backups são exclusivas do admin — na API (403) e no menu. Token do GitLab e URLs de banco de produção/homologação não são enviados a QA/Visualizador; o token nunca é reexibido na edição do projeto (em branco = manter o atual).
Os recursos de um ambiente derivam de env-<projeto>-<hash>:
container env-siart-web-front-3607143
rede net-env-siart-web-front-3607143
banco db_env_siart_web_front_3607143
imagem env-siart-web-front-3607143:latest
O projeto entra no nome porque o mesmo commit pode ser deployado por dois projetos diferentes (repositório único com Dockerfiles de front e back). Sem isso, o segundo deploy removeria o container e o banco do primeiro.
POST /api/environments cria (enfileira deploy)
PUT /api/environments/:id/variables edita as dependências
POST /api/environments/:id/restart reinicia o container
POST /api/environments/:id/redeploy refaz o pipeline (recria o banco)
POST /api/environments/:id/renew renova a validade
DELETE /api/environments/:id remove o ambiente
GET /api/environments/:id/deploy/stream progresso do deploy (SSE)
GET /api/environments/:id/logs/stream logs do container (SSE)
POST /api/projects/:id/variables cadastra/edita variável (upsert por chave)
DELETE /api/projects/:id/variables/:key remove variável
GET|PUT /api/settings configurações do sistema (ADMIN)
GET /api/servers lista os servidores de deploy (ADMIN)
POST /api/servers cadastra servidor (ADMIN)
PUT /api/servers/:id edita servidor — chave em branco = manter (ADMIN)
POST /api/servers/:id/test handshake com o engine do servidor (ADMIN)
DELETE /api/servers/:id remove servidor (ADMIN)
Quase sempre é o token do GitLab (Configurações → GitLab). A tela mostra o motivo exato devolvido pelo GitLab logo abaixo do campo:
| Mensagem | O que fazer |
|---|---|
| Token inválido ou revogado | Gere um novo PAT no GitLab e cole em Configurações. |
| Token sem permissão para este projeto | O PAT precisa dos escopos read_api e read_repository. |
| Projeto não encontrado | Confira o "ID do projeto GitLab" no cadastro do projeto. |
| Não foi possível falar com o GitLab | Rede/DNS/URL base — teste a URL base a partir do servidor. |
O PAT precisa de dois escopos: read_api (navegação pela API) e
read_repository (o clone durante o deploy). Só read_repository faz toda a
navegação falhar com 403 — ele libera apenas git clone, não a API REST.
Teste o token direto contra o servidor:
curl -s -o /dev/null -w '%{http_code}\n' \
-H "PRIVATE-TOKEN: <seu-token>" https://<seu-gitlab>/api/v4/user
# 200 = válido · 401 = inválido/revogadoFalhas de dependência externa respondem 502 (não 401), justamente para não serem confundidas com "sua sessão expirou" — o cliente rotacionaria o refresh token à toa.
BunJS · TypeScript · Express 5 · Prisma 7 · PostgreSQL · fila em SQLite
(bun:sqlite, sem Redis) · Docker ou Podman, local ou remoto por SSH ·
Traefik · Pi-hole · React + Vite + TailwindCSS + TanStack Query.
