Monalisa
Operação

Staging no Dokploy

Como staging é declarado e convergido (apply), publicado em ordem (deploy) e o que conferir com o auto-deploy ligado.

Staging roda no Dokploy como aplicações separadas, declaradas em infra/dokploy/src/staging.config.ts e convergidas por um script sobre a API do Dokploy. compose.yaml continua sendo a pilha local e a referência do que cada serviço precisa.

ServiçoTipo no DokployOrigem
postgresBanco (Postgres 18)postgres:18.6-alpine; senha gerada uma vez e mantida pelo Dokploy
redisBanco (tipo Redis, Valkey 8)valkey/valkey:8.1.10-alpine; senha gerada uma vez e mantida pelo Dokploy
elasticmqAplicação (imagem Docker)softwaremill/elasticmq-native:1.7.1, com elasticmq.conf montado
migratorAplicação (GitHub, alvo migrator)One-shot: restart none, update continue
workersAplicação (GitHub, alvo workers)45 s de graça na parada (orçamento de drenagem)
apiAplicação (GitHub, alvo runtime)Domínio HTTPS (Let's Encrypt) na porta 3000, 30 s de graça na parada
garageAplicação (imagem Docker)dxflrs/garage:v2.4.1, garage.toml montado, dois volumes, domínio S3 (Backups)
garage-initAplicação (imagem Docker)curlimages/curl:8.22.0 rodando o script de inicialização (one-shot)

As aplicações GitHub constroem o repositório novapromotora-labs/monalisa, branch main, com auto-deploy ligado (ver Auto-deploy).

Variáveis em três camadas

  • Projeto: valores comuns a todos os ambientes (pool e timeouts do banco, CORS_CREDENTIALS).
  • Ambiente staging: DATABASE_URL, REDIS_URL, o endpoint MONALISA_SQS_* do ElasticMQ, LOG_LEVEL, CORS_ORIGINS e o token de administração do Garage.
  • Aplicação: só o que cada uma usa, quase sempre como referência ${{project.NOME}} ou ${{environment.NOME}}, que o Dokploy resolve no deploy.

dokploy:apply: convergir

bun run dokploy:apply --dry-run   # só o plano: nomes de variáveis, nunca valores
bun run dokploy:apply             # converge; uma segunda execução informa "no changes"

A chave da API do Dokploy vem do cofre do operador e nunca é commitada, impressa ou registrada.

  • Encontra ou cria ambiente, bancos e aplicações pelo nome e altera só os campos que diferem.
  • Mescla cada camada de variáveis com o que o Dokploy já tem: variáveis que ele não gerencia sobrevivem. DATABASE_URL e REDIS_URL são reconstruídas a cada execução a partir da senha e do host que o Dokploy guarda, então acompanham um banco recriado.
  • Segredos gerados (PLATFORM_BOOTSTRAP_PASSWORD, os segredos do Garage e as senhas dos bancos na criação) só são criados quando faltam e nunca são sobrescritos. A saída nomeia variáveis, nunca valores.
  • Uma variável removida de staging.config.ts não é apagada do Dokploy: remova-a na interface.
  • A imagem de um banco só é atualizada no lugar dentro da mesma linha (repositório, major e variante: postgres:18.6-alpine → postgres:18.7-alpine). Outra linha para com erro: um major do PostgreSQL não lê o diretório de dados de outro, e o Valkey 8 não carrega o formato RDB do Redis 7.4. Nesses casos o serviço precisa de backup, remoção na interface e recriação pelo apply. O mesmo vale para o garage, preso a dxflrs/garage:v2.*: uma major do Garage muda o formato em disco.
  • Sem o domínio S3 configurado, o apply pula os backups com um aviso.

PLATFORM_BOOTSTRAP_PASSWORD é gerada pelo primeiro apply e fica só no ambiente do migrator. Ela não é impressa, a não ser com --reveal-bootstrap-password, que escreve só a senha no stdout e é recusado quando o stdout é um terminal. Só o primeiro deploy a lê.

dokploy:deploy: publicar em ordem

bun run dokploy:deploy   # a partir de um checkout de main em dia
  1. Sobe PostgreSQL e Redis se não estão rodando e espera o Dokploy reportá-los no ar; depois o ElasticMQ, se não está rodando.
  2. Publica o migrator. O status de deploy do Dokploy cobre o build e a atualização do serviço, não o código de saída de um one-shot; por isso o comando lê as tarefas do swarm do migrator e só continua quando a tarefa nova termina Complete. Tarefa falha ou rejeitada, saída diferente de zero, nenhuma tarefa nova ou resposta ilegível param o deploy antes dos workers e da API.
  3. Publica os workers e a API.
  4. Consulta https://<domínio>/ready até responder 200 e informar o latestMigration esperado: a migration mais nova do checkout local (por isso rodar a partir do commit que o Dokploy constrói), ou --expect-migration <nome>. Um 200 de uma tarefa antiga da API, ainda servindo durante a atualização, não passa.
  5. Só então cuida do armazenamento de backups: sobe o Garage se não está rodando e roda o garage-init (conferido como Complete do mesmo jeito) até ele ter terminado uma vez, ou de novo com --reinit. Uma falha do Garage ou do garage-init nunca segura a versão: o comando falha no fim, dizendo que a versão está no ar.

Cada requisição tem timeout e cada espera tem prazo de 20 minutos.

O migrator (como o garage-init) é one-shot e roda com restart none e update FailureAction: continue, Monitor: 0. Sem isso, uma tarefa que termina com 0 dentro da janela de monitoramento contaria como atualização falha e o swarm voltaria o serviço à especificação anterior (com um DATABASE_URL antigo), e quem decide se tenta de novo é o comando de deploy, não o swarm.

Auto-deploy

O auto-deploy fica ligado por padrão para o migrator, a API e os workers (autoDeploy em staging.config.ts; dokploy:apply --no-auto-deploy desliga até o próximo apply sem a flag). A cada push em main, o Dokploy republica os três, cada um por conta própria, sem ordem entre eles.

O portão de prontidão garante a correção: a API e os workers novos ficam não prontos (/ready responde 503, os workers não consomem) até o migrator aplicar a migration do build, então uma API nova nunca serve sobre um schema antigo.

Ele não garante que o deploy convirja:

  • ninguém lê o código de saída do migrator, então um migrator que falha passa despercebido;
  • uma tarefa nova da API que continua não pronta além do healthcheck, enquanto o migrator ainda roda, pode deixar a atualização do swarm parada na tarefa antiga (o Dokploy não define ação de falha de atualização para a API e os workers).

Por isso, depois de um push, confira a versão: o /ready precisa informar o latestMigration novo. Se não informar, republique a API (ou o migrator, se ele falhou), ou rode bun run dokploy:deploy, o caminho ordenado que confere cada passo. Se a instância do Dokploy tiver notificações configuradas, ligue a de deploy falho.

Quando a versão adiciona uma task ou um assinante, os workers podem subir depois da API; mensagens para uma unidade ainda desconhecida voltam para a fila com backoff (ver Ordem de deploy).

Outros cuidados

  • O deploy não mexe num ElasticMQ nem num Garage que estão rodando: uma mudança em elasticmq.conf (gravada pelo apply) só vale depois de republicar o elasticmq na interface; o mesmo vale para garage.toml e o garage.
  • As filas do ElasticMQ são em memória: reiniciá-lo perde mensagens que o relay já enviou, e as runs delas ficam em queued.
  • Staging não tem agendador externo, então tarefas agendadas (como maintenance.purge) não rodam lá.
  • O /ready expõe latestMigration publicamente; produção precisa reavaliar isso.

Nesta página