Migrar docker-compose pro runner
Este guia cobre o caminho completo de tirar um projeto que roda via docker compose up direto no servidor e colocar ele sob o runner — preservando a capacidade de regenerar os .deploy.yml quando o compose mudar upstream (via runner reconcile).
Aplica-se a v2.26.10+. Versoes anteriores tem gaps conhecidos — veja changelog (v2.26.1 a v2.26.10) para a lista completa de fixes. Para projetos sem compose, veja Wizard CLI.
Antes de comecar
Precondicoes:
- Runner v2.26.10+ instalado no servidor de deploy (
runner self-update) - Repo com
docker-compose.yml(e opcionalmentedocker-compose.override.yml/.prod.yml) - Acesso de escrita no repo (vai commitar os
.deploy.ymlgerados +.runner/wizard-answers.yml)
Se o repo tem multiplos compose files (caso de overrides por ambiente), o runner faz merge per compose-spec semantics — exatamente igual ao docker compose -f base.yml -f override.yml.
Capacidades cobertas (v2.26.10)
Tudo abaixo e traduzido automaticamente pelo wizard --from-compose:
| Compose | Manifest | Notas |
|---|---|---|
command: (string ou array) |
command: [...] |
Args apos entrypoint. v2.26.3+ |
entrypoint: |
entrypoint: [...] |
Override de ENTRYPOINT. v2.24.0+ |
environment: [VAR=${X:-default}] |
Expandido pra valor literal | Le shell env no parse. v2.26.3+ |
volumes: ['./X:/etc/X'] |
Resolve a src/<inst>/<v>/X se existir |
Bind paths matching compose. v2.26.2+ |
networks: [meet] (user-defined) |
networks: [<prefix>_meet] + alias |
Apps do mesmo prefix se veem. v2.26.3+ |
networks: [mysql, redis] (shared infra) |
passa unchanged | Cross-project. v2.26.3+ |
deploy.resources.limits.memory |
resources.memory |
Cpus, memory, swap. v2.24.0+ |
cap_add, cap_drop, devices |
security.*, devices |
v2.24.0+ |
logging.driver |
logging.driver |
v2.24.0+ |
tmpfs, expose, dns, hostname |
mesmo nome no manifest | v2.24.0+ |
Nao cobertos (halt no wizard, ou drop com notice): privileged: true, network_mode: host, sysctls, pid/ipc/uts: host, userns_mode, group_add, extra_hosts (use hosts: no manifest), profiles, extends, deploy.replicas, deploy.placement.
Mental model: 3 camadas de configuracao
Antes de mexer em qualquer arquivo, entenda como o runner separa responsabilidades:
| Camada | Arquivo | Conteudo | Quem edita |
|---|---|---|---|
| Estrutural | docker-compose.yml (upstream) |
Port real, healthcheck endpoint real, env vars do app, volumes, networks | Dev + Ops |
| Runner-specific | .runner/wizard-answers.yml |
Domain por instance, system label, instance name, ckey, branch | Ops (via prompt do wizard) |
| Gerado | <service>/.deploy.yml |
Combinacao das 2 camadas acima | NUNCA edita a mao |
Esse split eh o que permite runner reconcile funcionar — o reconcile re-roda o mapper com a combinacao compose + answers e regenera o .deploy.yml. Se voce editar .deploy.yml direto, o reconcile sobrescreve suas edits.
Fluxo recomendado
1. Ajustar o `docker-compose.yml` upstream pra ter declaracoes estruturais corretas
Antes de rodar o wizard, garanta que o compose tem:
services:
agent:
expose: ["8787"] # Porta real do app (default 80 se ausente)
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8787/healthz"]
interval: 30s
timeout: 5s
retries: 3Por que adicionar healthcheck no compose:
- Beneficia dev local —
docker compose upja mostra(healthy)nodocker ps - Wizard traduz pro
.deploy.ymlautomaticamente - Reconcile preserva (vem do compose, nao das answers)
Atencao com localhost: em imagens Alpine (que muitos services usam), localhost resolve pra ::1 (IPv6) e o healthcheck falha. Sempre prefira 127.0.0.1 em healthchecks.
2. Rodar o wizard SEM `--accept-defaults`
cd /opt/meu-projeto
runner wizard --path . --from-compose \
-f docker-compose.yml \
-f docker-compose.prod.yml(Sem -f o runner auto-detecta docker-compose.override.{yml,yaml} ao lado da base.)
O wizard pergunta service-por-service:
Service 'frontend' (build: ./frontend):
domain (empty = port-only) [default: empty]: meet.example.com
system (sys|front|api|worker) [default: sys]: front
instance name [default: production]:
Service 'agent' (build: ./agent):
domain (empty = port-only) [default: empty]: # internal, leave blank
system [default: sys]: worker
instance name [default: production]:
Service 'livekit-server' (image: livekit/livekit-server:v1.8.3):
domain [default: empty]: livekit.example.com
system [default: sys]: sys
instance name [default: production]:Resultado salvo em .runner/wizard-answers.yml:
schema_version: 1
project_prefix: meu-projeto
services:
frontend:
wizardize: true
domain: meet.example.com
system: front
instance_name: production
agent:
wizardize: true
domain: null
system: worker
instance_name: production
livekit-server:
wizardize: true
domain: livekit.example.com
system: sys
instance_name: productionPor que --accept-defaults eh ruim aqui: pula os prompts e gera instance.domain: null pra todos os services. Se voce edita o domain direto no .deploy.yml, o reconcile vai apagar.
3. Conferir os `.deploy.yml` gerados
ls */.deploy.yml
# agent/.deploy.yml
# frontend/.deploy.yml
# livekit-server/.deploy.yml
grep -E "port|healthcheck|domain|resources" agent/.deploy.ymlVerifique:
port:bateu com oexpose:do compose (ou foi inferido deports:)healthcheck.pathouhealthcheck.cmdreflete o endpoint realinstance.production.domainesta correto (do wizard-answers)resources.*(se compose tinhadeploy.resources.limits.*oumem_limit/cpus) foi traduzido
Se algo estiver estruturalmente errado (port errado, healthcheck path errado), volte ao passo 1: ajuste o compose, re-rode o wizard. Nao edite .deploy.yml.
Se algo for runner-specific errado (domain, instance), edite .runner/wizard-answers.yml e rode runner reconcile <app> apos o registro.
4. Commit tudo
git add docker-compose.yml docker-compose.prod.yml # mudancas estruturais
git add agent/.deploy.yml frontend/.deploy.yml livekit-server/.deploy.yml
git add .runner/wizard-answers.yml # critico — reconcile depende
git commit -m "feat: runner-managed deploy via wizard --from-compose"
git push origin dist # ou main, dependendo do flow.runner/wizard-answers.yml deve ser commitado. Sem ele, ninguem mais consegue rodar runner reconcile consistentemente.
5. Registrar cada app no runner (`--force-duplicate` do 2o em diante)
runner add --repo user/repo --branch dist --ckey \
--manifest-path agent/.deploy.yml \
--instance staging
runner add --repo user/repo --branch dist --ckey \
--manifest-path frontend/.deploy.yml \
--instance staging \
--force-duplicate
runner add --repo user/repo --branch dist --ckey \
--manifest-path livekit-server/.deploy.yml \
--instance staging \
--force-duplicateComece em staging. Nao registre direto em production. Valide pelo menos 1 semana.
6. Setar secrets
runner secrets set agent LIVEKIT_API_KEY=...
runner secrets set agent OPENAI_API_KEY=...
# ... etcVITE_* / build args ja foram capturados pelo wizard em build.args do .deploy.yml. Esses sao build-time, nao runtime — nao precisam ir em secrets.
7. Deploy
runner deploy agent --instance staging --force
runner deploy frontend --instance staging --force
runner deploy livekit-server --instance staging --forceManutencao continua (reconcile)
Quando o time mexer no docker-compose.yml (adicionar service, mudar port, ajustar env), regere os manifests:
git pull origin main
runner reconcile agent --apply
runner reconcile frontend --apply
runner reconcile livekit-server --apply
git diff # confere o que mudou nos .deploy.yml
git commit -am "chore: reconcile after compose update"Reconcile preserva o que esta em wizard-answers.yml (domains, instance config). Soh muda o que veio do compose.
Erros comuns
"wizard halted on service X"
Service usa feature compose que o runner nao mapeia (privileged, sysctls, network_mode: host, etc).
Opcoes:
- Fail-soft default (v2.26.0+): os outros services geram normal, soh esse foi pulado. Veja o output do wizard — ele lista quais services foram skipped e por que.
--ignore-eccentric <field>ou--ignore-eccentric all: forca a geracao mesmo com a feature. Use com cuidado — o.deploy.ymlresultante ignora silenciosamente a feature.- Editar o compose: remover a feature problematica, talvez substituir por algo que o runner suporte.
"no compose file found"
Wizard nao encontrou docker-compose.yml ou compose.yml no --path. Passe explicito:
runner wizard --from-compose -f path/to/my-compose.yml
"service '' missing from merged compose"
Aconteceu durante runner reconcile. Significa que o compose mudou e o service que o app tinha base nao existe mais. Resolva:
- Restaure o service no compose, OU
- Migre a app pra um
.deploy.ymlautorado a mao:runner unregister <name>e re-crie sem o--from-compose.
Domains ficaram `null` apos reconcile
Voce rodou wizard com --accept-defaults originalmente, dominios nunca foram pra wizard-answers.yml. Solucao:
- Edite
.runner/wizard-answers.ymla mao, adicionando os domains - Rode
runner reconcile <app> --apply
Deploy haltou com "manifest drift detected" (v2.26.7+)
Sintoma:
Error: manifest drift detected for service 'agent' — re-running the wizard
mapper with the current `wizard-answers.yml` would produce a DIFFERENT
`.deploy.yml` than what's on disk.
Fields divergentes (current → expected):
networks:
current: ["meet"]
expected: ["meet_meet"]
...
Fix: runner reconcile meet-agent --applyO que isso significa: o manifest no disco (e no upstream) esta stale relativo ao wizard-answers.yml. Causas tipicas:
- Manifest foi gerado por wizard antigo (pre-v2.26.3, antes do project-prefix em networks). Solucao:
runner reconcile <app> --applyregenera; commitar upstream pra resolver permanentemente. wizard-answers.ymlfoi editado (project_prefix, system, domain) mas reconcile nunca rodou.- Algum outro service do mesmo wizard run foi reconciliado (atualizando
wizard-answers.yml) mas este nao.
Fix:
runner reconcile <app> --apply
runner deploy <app>Pra evitar reincidencia: commitar os manifests regenerados upstream:
# No dev (que tem o repo clonado):
git pull
# Manifestos regenerados estao em /data/apps/<app>/.deploy.yml — copie pro repo:
scp prod-server:/data/apps/<app>/.deploy.yml ./<service>/.deploy.yml
git add */.deploy.yml && git commit -m "chore: reconcile post-v2.26.7"
git pushApos o commit, qualquer outro servidor que rodar runner add ja vem com o manifest correto — sem reconcile recorrente.
Frontend Vite/Next/CRA crasha com "Invalid URL" ou bundle sem URLs
Sintoma: console do browser mostra erros tipo Uncaught Error: Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL. apos deploy. Bundle JS no container nao tem as URLs esperadas:
docker exec meet_frontend_production_<hash> sh -c \
'grep -ohE "https://[a-z0-9]+\\.supabase\\.co" /usr/share/caddy/assets/*.js | sort -u'
# (vazio)Causa: build.args do manifest contem placeholders ${VAR} que nao foram expandidos no docker build. Vite (e similares) precisam dos valores em tempo de build, nao em tempo de runtime — bundles ja saem compilados.
Fix v2.26.10+: runner expande ${VAR} em 3 camadas:
- Shell env no wizard time (
runner wizard --from-compose): se voce setar a env var antes, ela e bake-in no manifest. runner env setapos wizard: valor vai pro.envdo app. Runtime expansion no deploy time pega via.secrets > .env > shell env.- Literal preservado: se nenhuma fonte tem o valor, manifest mantem
${VAR}literal (em vez de virar empty string) — operador detecta no manifest ao inves de bundle silenciosamente vazio.
Workflow recomendado (Vite/Next/CRA):
# Defina os valores via runner env set (ou direto no .env do app)
runner env set <app> --key VITE_SUPABASE_URL --value "https://x.supabase.co"
runner env set <app> --key VITE_SUPABASE_ANON_KEY --value "eyJ..."
runner env set <app> --key VITE_LIVEKIT_WS_URL --value "wss://livekit.example.com"
# Reconcile garante o manifest e consistente com state (drift check)
runner reconcile <app> --apply
# Deploy --force pra bypass docker layer cache (build args mudam → bundle re-compila)
runner deploy <app> --forceValidacao:
docker exec <container> sh -c \
'grep -ohE "https://[a-z0-9]+\\.supabase\\.co" /usr/share/caddy/assets/*.js | sort -u'
# https://x.supabase.co ✓Container nao resolve sibling por service-name
Sintoma: agent app loga gaierror: Temporary failure in name resolution ao tentar ws://livekit:7880.
Em v2.26.4+, sibling apps devem se resolver por nome curto (igual docker compose). Causa comum:
- Apps registrados antes da v2.26.3 —
network_aliases:nao foi populado nos manifests. Roderunner reconcile <app> --applyem cada app +runner deploy <app>pra picar a nova network compartilhada. - Apps em runner v2.26.3 — primeiro deploy entrou na network nova, mas deploys subsequentes pegaram a velha (Bug 12 re-open). Atualize pra v2.26.4+ (
runner self-update) e re-deploy cada app:runner self-update runner fetch <app> runner reconcile <app> --apply runner deploy <app> project_prefixdivergente — apps com prefixes diferentes emwizard-answers.ymlnao compartilham network. Confira que todos os manifests do mesmo monorepo tem o mesmoproject_prefix(regenere se necessario comwizard --from-composedo mesmo--path).- Network e reserved/shared —
mysql,redis, etc. nao recebem aliases (sao cross-project). Usenetworks: [meu_app]no compose pra forcar uma network user-defined que ganhe scoping.
`runner reconcile` falha com "compose file missing" apos um deploy
Sintoma (pre-v2.26.4):
$ runner reconcile <app> --apply
Error: compose file missing: /data/apps/<app>/pull/docker-compose.yml
Run `runner fetch <app>` first to update pull/.Em v2.26.4+, runner deploy deixou de limpar pull/ no fim. Se voce esta em v2.26.4+ e ainda ve esse erro, e porque a app foi deployada em uma versao mais antiga e o pull/ ja tinha sido removido. Solucao: runner fetch <app> (com bootstrap automatico de pull/ desde v2.26.1+).
`runner deploy` reclama de "Another deploy is in progress" mas nao tem deploy rodando
Pre-v2.26.4, sem flag de recovery. Em v2.26.4+:
# 1. Confirme que nao ha deploy real rodando
ps -ef | grep "runner deploy <app>"
# 2. Force a remocao do lock
runner deploy <app> --force-unlockStale-detection (PID gone OR > 30min) deve cobrir o caso comum automaticamente. --force-unlock e pra quando a heuristica nao dispara (e.g., PID foi reciclado).
First deploy falhou e a porta ficou alocada
Sintoma (pre-v2.26.4): tentando docker compose up apos um runner deploy falho retorna:
Error response from daemon: Bind for 0.0.0.0:7880 failed: port is already allocatedEm v2.26.4+, o container quebrado e removido automaticamente no failure path. Se voce esta em v2.26.4+ e ainda ve isso, e porque o container ja existia de uma versao anterior do runner. Limpe manualmente:
docker ps -a --filter "name=<project>_<instance>_" --format "{{.Names}}" | xargs -r docker rm -f`runner env set` nao tem efeito
Pre-v2.26.3, manifest environment: sempre vencia. Atualize pra v2.26.3+ e re-deploy.
Em v2.26.3+, ordem e: .env/.secrets (operador) > environment: (manifest). Verifique se runner env set escreveu de fato:
runner secrets show <app> # mostra env + secrets atuais
docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep <KEY>Variaveis `${...}` aparecem literais no container
Pre-v2.26.3, wizard copiava ${VAR:-default} literal pro manifest. Atualize e regenere os manifests:
git pull # pega o compose upstream
runner reconcile <app> --apply
runner deploy <app>Wizard agora expande ${VAR}, ${VAR:-default}, ${VAR-default}, ${VAR:?error} lendo do shell env no momento da geracao. Se quiser forcar valores no wizard, exporte antes:
LIVEKIT_PORT=7880 runner wizard --from-compose ...Quando NAO usar `--from-compose`
- App nao tem compose (Dockerfile direto) → use
runner wizardinterativo normal - Compose tem features que o runner nao suporta E sao essenciais (privileged + GPU advanced + multi-host) → autore
.deploy.ymla mao - App eh single-service e simples →
runner wizarddireto poupa o ceremony do multi-file
Ver tambem
- Wizard CLI reference — todos os flags
- Reconcile CLI reference — fluxo de regeneracao
- `.deploy.yml` reference — schema completo
- Multi-service no mesmo repo — recuperação de ckeys divergentes quando N services do mesmo compose viram N apps