runner deploy

Executa o pipeline de deploy completo para uma aplicacao ou todas as apps registradas.

Sintaxe

runner deploy <app>
runner deploy <app> [--version <version>]
runner deploy --all

Parametros

Parametro Tipo Obrigatorio Descricao
<app> string Sim* Caminho ou nome do projeto (ex: meu-app ou /data/apps/meu-app)
--version, -v string Nao Versao especifica para deploy
--image-tag <tag> string Nao (v2.173.0+) App com source registry: tag da imagem a implantar. O runner resolve a tag em digest antes do deploy, e a tag vira a versao. Recusado em app que nao e registry (image_tag_sem_registry). Nao combina com --all, --version nem --local. Ver Deploy por imagem
--instance, -i <nome> string Nao (v2.174.0+) Confere que a instancia pedida e a do registro da app. Se for outra, o deploy falha antes de qualquer efeito (inclusive antes de gravar a ckey) com instancia_divergente. Nao move a app: para isso, runner switch-instance. Nao combina com --all
--force, -f flag Nao Forca redeploy mesmo se versao ja implantada
--skip-tests flag Nao Pula testes apos deploy
--verbose, -V flag Nao Output detalhado com logs tecnicos
--all flag Nao Deploya todas as apps registradas
-x, --exact flag Nao Desliga a resolucao por query: <app> vira nome exato, um alvo so
-y, --yes flag Nao Confirma a operacao quando a query casa varios alvos do mesmo projeto. Nao autoriza atravessar projetos
--port <N> int Nao Override de porta (v2.19.0). Persiste no state — re-deploys reusam.
--no-port flag Nao Desabilita port mapping (strategy=none) so desta deploy (v2.19.0).
--insecure flag Nao Bypass de pre-flight de secrets vazios (v2.19.0, NÃO RECOMENDADO).
--wait-lock[=SEG] opção Nao v2.160.0+. Espera o lock da app em vez de falhar com lock_contended. Sem valor, até 600 s. Avisa uma vez quem segura. Para loops de manutenção que cruzam com o cron
--force-unlock flag Nao Remove .deploy.lock antes de tentar o deploy (v2.26.4). Use quando stale-detection (PID gone OR > LOCK_MAX_AGE) nao disparou e o deploy ainda diz "Another deploy is in progress".
--allow-missing-required flag Nao (v2.32.0+) Bypass do pre-flight de variaveis required: declaradas no manifest. Use apenas em rollouts de emergencia onde voce sabe que o container vai subir com placeholders.
--local flag Nao (v2.52.0+) Deploya o estado que esta no disco sem re-sincronizar do repo. Pula o git reset do pull/ e o refresh do .deploy.yml a partir do pull/. Use quando editou arquivos localmente e quer deploya-los sem que o runner os reverta pra versao do repo. Nao combina com --all.
--skip-health flag Nao (v2.54.0+) Pula a validacao de saude so nesta invocacao — equivale a healthcheck: mode: skip_check porem sem tocar no manifesto. Use quando o container sobe mas ainda nao ha Traefik/healthcheck valido no server. Redeploy futuro revalida sem reinstalar.

*Use <app> ou --all, nao ambos.

Qual instancia e deployada (v2.55.1+)

A instancia efetiva e resolvida nesta ordem — a primeira que existir vence:

Prioridade Consulta Fonte
1 state.instance state do registro em /opt/runner/state/<app>.yml, fixado por runner add --instance <N>
2 default do manifesto .deploy.yml: production se declarada, senao a primeira instancia de instances:
3 production (literal) fallback quando o manifesto nao declara instancias

runner deploy, runner deploy --all e runner fetch --deploy usam a mesma resolucao a partir da v2.55.1. Antes, os deploys manuais ignoravam o passo 1 e miravam sempre o default do manifesto.

Sem production e sem state.instance, o passo 2 usa a primeira instancia declarada no .deploy.yml (deterministico desde a v2.55.2 — instances preserva a ordem do YAML). Ainda assim, para deixar explicito, declare production, registre com --instance, ou use um manifesto por deploy (veja Dois deploys da mesma branch).

Deploy do estado local (`--local`)

Por padrao, todo runner deploy trata o repo como fonte da verdade: antes de subir o container ele re-sincroniza o pull/ (via git reset --hard) e re-copia o .deploy.yml do pull/ pra raiz. Isso sobrescreve edicoes locais — se voce editou o .deploy.yml na maquina e rodou deploy --force, sua mudanca e revertida pra versao do repo.

O --local deploya exatamente o que esta no disco, sem essa re-sincronizacao:

runner deploy meu-app --local --force
  • Naquele deploy o disco e soberano; nao persiste (o proximo deploy sem --local volta a sincronizar do repo).
  • Para uma mudanca duravel, commite no repo (branch dist) — e o fluxo normal.
  • Para .env/.secrets especificamente, use env_sync: skip no manifesto (torna os locais soberanos de forma permanente).

Pular a validacao de saude (`--skip-health`) (v2.54.0+)

Por padrao o deploy so e declarado bem-sucedido quando o healthcheck passa (fase 5). Se falhar, o runner faz rollback. Isso e o correto — mas ha um caso em que voce quer subir mesmo assim: o container sobe, porem ainda nao ha Traefik ou healthcheck valido no server (ambiente sendo montado). Sem uma valvula de escape, o deploy fica preso em rollback.

O --skip-health pula a validacao de saude so naquela invocacao:

runner deploy meu-app --skip-health
  • Equivale a healthcheck: mode: skip_check, porem sem editar o manifesto do repo.
  • A app e declarada OK sem gating, o container sobe e o Traefik e roteado normalmente.
  • Nao persiste: um redeploy futuro (ja com o ambiente pronto) volta a validar a saude — sem reinstalar a app.
  • Emite um warn no log ([HEALTH] --skip-health: pulando a validacao...) e o evento de notificacao HealthSkipped.

Diferenca em relacao a mode: skip_check no .deploy.yml: aquele desliga a saude permanentemente para a app (todo deploy). O --skip-health e pontual — use quando o skip e temporario. Para desligar de forma duravel, prefira o modo no manifesto.

Tambem disponivel no primeiro deploy via runner add --deploy --skip-health (veja `runner add`).

Exemplos

Deploy Padrao

runner deploy --app /data/apps/usuario_meu-app

Deploy por Nome

runner deploy meu-app

Deploy de Versao Especifica

runner deploy meu-app --version v1.2.3

Deploy de uma instancia especifica

Ate a v2.173.0 runner deploy nao tinha flag --instance. Desde a v2.174.0 ela existe, mas so confere: a instancia continua vindo da resolucao abaixo (fixada no registro via runner add --instance <N>), e uma instancia diferente da registrada falha com instancia_divergente. Veja Qual instancia e deployada.

# registra o app ja fixando a instancia
runner add --repo usuario/meu-app --branch main --instance staging --ckey "<ckey>"
# a partir daqui, o deploy honra staging automaticamente
runner deploy meu-app

# confere a instancia (v2.174.0+); falha com instancia_divergente se a app nao for staging
runner deploy meu-app --instance staging

Deploy sem Testes

runner deploy meu-app --skip-tests

Deploy Forcado (Redeploy)

runner deploy meu-app --force

Em modo IMAGE, --force puxa a imagem mais recente do registry antes de recriar o container. Exceção (v2.50.0): com pull_policy: never no manifesto (imagem carregada localmente via docker load, sem registry), o pull é pulado com log informativo — antes ele tentava e falhava com WARN em todo deploy forçado.

Deploy com Output Detalhado

runner deploy meu-app --verbose

Deploy de Todas as Apps

runner deploy --all

Override de Porta (v2.19.0)

# Override permanente (persiste no state — re-deploys reusam)
runner deploy myapp --port 9000

# Sem port mapping (strategy=none, container so na rede docker)
runner deploy myapp --no-port

--port N sobrescreve qualquer alocacao sticky existente e persiste em state.network.published_port. Use isso pra "fixar" uma porta que voce quer permanente. As deploys subsequentes (manuais ou via fetch --deploy) vao reusar a mesma porta sem precisar --port de novo.

--no-port desabilita port mapping so desta deploy — nao persiste. Util pra debugging ou pra apps que so precisam estar na rede Docker (sem expor pro host).

Veja `runner ports` pra inspecionar/liberar/realocar portas alocadas.

Pre-flight de secrets vazios (v2.19.0)

Antes do container subir, o runner valida que cada secret declarado em .deploy.yml::secrets: tem valor nao-vazio em .runner/secrets.enc (apos decifrar). Se algum esta vazio, o deploy aborta:

runner deploy minha-api
# Error: deploy_blocked: secrets declared in .deploy.yml have empty values: ["APP_KEY"]
#        Bypass: rerun with --insecure (NOT recommended).
#        Or fill the values:
#          runner env set <app> --key APP_KEY --secret --value <value>

Resolução padrão:

runner env set minha-api --key APP_KEY --secret --value "base64:$(openssl rand -base64 32)"
runner deploy minha-api  # agora passa

Bypass: runner deploy minha-api --insecure (so use conscientemente — geralmente significa que o app vai subir quebrado em runtime).

Resolve o caso onde Laravel /up retornava 200 mesmo com APP_KEY vazio, fazendo o runner declarar deploy bem-sucedido enquanto / retornava 500.

Pre-flight de variáveis obrigatórias (v2.32.0+)

O manifest aceita um campo required: no topo declarando variáveis que devem resolver pra valor não-vazio em .env ou .secrets antes do deploy começar:

required:
  - NEXT_PUBLIC_SUPABASE_ANON_KEY
  - DB_PASSWORD

Se alguma estiver ausente ou vazia, runner deploy bailha fatal antes de tocar Docker:

runner deploy web-front
# Error: missing_required: variables required by the manifest are unset or
# empty: NEXT_PUBLIC_SUPABASE_ANON_KEY, DB_PASSWORD.
# Set them with `runner env set <app> <KEY> <VALUE>` (or `--secret -f` for
# secrets), then retry the deploy.

Resolução padrão:

runner env set web-front NEXT_PUBLIC_SUPABASE_ANON_KEY anon-key-value
runner env set web-front DB_PASSWORD --secret -f "s3cr3t"
runner deploy web-front  # agora passa

Bypass de emergência: runner deploy web-front --allow-missing-required (use apenas quando você sabe que o container vai subir com placeholders e isso é intencional).

Por que existe: o checker fecha o cenário onde Next.js (createSupabaseClient(URL, KEY) no middleware) crasha em startup com chave vazia → toda request 500 → healthcheck r.status<500 falha eternamente. Pré-v2.32.0, o runner ficava ~173s em retry loop antes de rolar back. Agora bailha em <1s.

`runner env validate ` como pre-deploy check

Sem --required na linha de comando, runner env validate cai no required: do manifest automaticamente:

runner env validate web-front
# Error: missing_required: ... NEXT_PUBLIC_SUPABASE_ANON_KEY, DB_PASSWORD.

Use no seu pipeline CI antes de invocar deploy.

Util apos recovery do servidor. A partir da v2.1.0, o --all faz topological sort baseado no campo depends_on: do .deploy.yml de cada app. Apps sem dependencias sao deployadas primeiro; apps cujas dependencias falharam sao automaticamente puladas.

Ciclos de dependencia (a -> b -> a) sao detectados e abortam o --all com mensagem listando os projetos envolvidos.

# Forca redeploy de todas
runner deploy --all --force

# Com output detalhado
runner deploy --all --verbose

Output do Deploy

Formato Padrao

O deploy exibe progresso com badges indicando cada etapa:

[1/6] [PREPARE]   Preparando deploy...
[2/6] [ARTIFACT]  Copiando artefatos...
[3/6] [ENV]       Gerando ambiente...
[4/6] [SECURITY]  Verificando segurança...
[5/6] [CONTAINER] Subindo container de teste...
[6/6] [PROMOTE]   Promovendo versao...

═══════════════════════════════════════
  ✓ DEPLOY CONCLUIDO
  App:     meu-app/production
  Versao:  v1.2.0
  URL:     https://app.meusite.com.br
═══════════════════════════════════════

Versao Ja Implantada

Se a versao ja esta implantada:

═══════════════════════════════════════
  ✓ JA IMPLANTADO
  App:     meu-app/production
  Versao:  v1.2.0

  Use --force para reimplantar
═══════════════════════════════════════

Modo Verbose (-V)

O modo verbose exibe logs tecnicos detalhados:

runner deploy meu-app --verbose

Inclui:

  • Versao do Runner
  • Caminho do config
  • Logs de nivel INFO
  • Detalhes de cada operacao

Pipeline de Deploy

O comando executa as seguintes etapas:

  1. Prepare

    • Verifica configuracao (.deploy.yml)
    • Extrai versao do projeto
    • Cria diretorio de versao
  2. Artifacts

    • Copia artefatos para diretorio de versao
    • Limpa workdir (remove arquivos desnecessarios)
  3. Environment

    • Processa templates de ambiente
    • Gera .env e .secrets
    • Encripta e persiste secrets no repo (se ckey configurada)
  4. Test (Blue-Green)

    • Sobe container de teste
    • Executa health check
    • Executa testes de seguranca
    • Executa Playwright (se configurado)
  5. Promote

    • Atualiza symlink current
    • Gera Traefik dynamic config
    • Roteia trafego para nova versao
  6. Cleanup

    • Remove containers de teste
    • Remove versoes antigas
  7. Notify

    • Envia notificacao (Telegram/Discord)

Comportamento

Rollback Automatico (v1.4.1)

O Runner faz rollback automatico dependendo da fase em que o deploy falha:

Fase que falha Impacto Rollback
Fetch/Prepare (fases 0-1) Container antigo nao foi tocado Nenhum necessario — versao anterior continua ativa
Build (fase 3) Container antigo nao foi tocado Nenhum necessario — versao anterior continua ativa
Start (fase 4) com portas Container antigo foi parado Automatico — container anterior reiniciado
Start (fase 4) com Traefik Container antigo nao foi tocado Nenhum necessario — trafego continua na versao anterior
Health check (fase 5) Container novo subiu mas nao e saudavel Automatico — novo container removido, anterior reiniciado

Em todos os casos, a notificacao de falha indica qual versao ficou ativa.

Cleanup do container quebrado (v2.26.4+)

Quando o Start da fase 4 falha sem versao anterior pra rollback (first deploy), o runner agora remove o container quebrado antes de retornar erro. Pre-v2.26.4, ele ficava UP segurando portas — bloqueava docker compose up paralelo com Bind for 0.0.0.0:PORT failed: port is already allocated.

Removal e best-effort (warn-only se falhar). Inspecione os logs antes de retentar:

docker logs <container_name>   # logs do container que falhou (ainda disponiveis ate cleanup)
runner deploy <app> --verbose  # retry com output completo

Lock recovery (v2.26.4+)

Cada deploy adquire <app_dir>/.deploy.lock. A stale-detection libera o lock automaticamente quando:

  • O PID gravado nao existe mais (processo morreu sem release)
  • O lock e mais velho que LOCK_MAX_AGE_MINUTES (default 30min)

Se nenhuma dessas heuristicas dispara mas voce SABE que o deploy esta preso:

runner deploy <app> --force-unlock
# [deploy] --force-unlock: removed /data/apps/<app>/.deploy.lock
# (deploy continua normal apos a remocao)

Quando NAO usar --force-unlock: se houver um runner deploy real rodando em outra sessao. Confirme com ps -ef | grep "runner deploy <app>" antes.

Protecao contra Retry Infinito (v1.4.1)

Quando o deploy automatico (fetch --deploy) falha, o Runner registra o commit que falhou no estado da app. Se o mesmo commit falhar novamente, o contador de retries incrementa. Ao atingir max_deploy_retries (padrao: 3), o Runner bloqueia o deploy automatico daquele commit.

Commit abc123 → deploy falhou (1/3)
Commit abc123 → deploy falhou (2/3)
Commit abc123 → deploy falhou (3/3)
Commit abc123 → bloqueado (aguardando novo push)
Commit def456 → novo commit, contador resetado → deploy executa

Para configurar o limite:

# /opt/runner/config.yml
max_deploy_retries: 3   # padrao: 3, 0 = ilimitado

O deploy manual (runner deploy meu-app) nao e afetado pelo limite — sempre executa.

Health Check

O health check verifica:

  • Container iniciou
  • Endpoint /health retorna 200
  • Container esta healthy

O timeout e calculado automaticamente com base nos parametros de healthcheck do .deploy.yml:

timeout = start_period + (interval + timeout) * retries + buffer

Notificacoes

Mensagens enviadas:

  • Deploy Iniciado — ao comecar o pipeline
  • Deploy Concluido — ao finalizar com sucesso
  • Deploy Falhou — em caso de erro (inclui versao anterior ativa e erro extraido)

A mensagem de falha extrai a linha de erro real de outputs verbosos (Docker BuildKit, npm, compilador Rust) em vez de mostrar o output truncado.

Exemplo de notificacao de falha:

❌ Deploy Falhou

📦 Projeto: meu-app
🌐 Instance: production
🏷️ Versão: abc1234
🔄 Rollback: v1.0.0 (versão anterior mantida)

⚠️ Erro:
docker build failed: ERROR: process "/bin/sh -c npm run build" did not complete successfully

Exit Codes

Codigo Significado
0 Deploy concluido
1 Deploy falhou (healthcheck, testes, build, rollback, lock, app inexistente: qualquer falha antes de o deploy concluir)
3 Deploy concluido com pendencia: o container esta no ar, healthy, e o estado foi gravado, mas um passo pos-deploy falhou (ex.: push dos secrets cifrados, manifesto_repo_divergiu). Nao e falha: nao tente de novo
13 manifesto_fixado: a operacao reescreveria o manifesto fixado no servidor e foi recusada antes de qualquer efeito (na 2.180.0 sai de runner upgrade e do manifest write sem --local)
17 manifesto_fixado_adulterado: a raiz <app>/.deploy.yml nao e o manifesto fixado. O deploy (e o rollback) nao comecou

Erro de uso (flag desconhecida, argumento faltando) sai 2, do parser de argumentos, antes de qualquer deploy.

Ate a 2.148.x o deploy com pendencia saia 1, igual a deploy falho; o 3 existe desde a 2.149.0. Os exits 13 e 17 existem desde a 2.178.0 (manifesto fixado; ver `runner manifest`).

Comandos de varias apps (fetch --deploy, deploy --all, deploy-all) mantem a regra de exit que ja tinham: ali a pendencia conta como sucesso com aviso. A app nao pausa nem soma retentativa.

Linha `RUNNER_RESULT`

Com RUNNER_RESULT_LINE=1 no ambiente, todo comando de deploy (deploy, deploy-all, fetch --deploy, add --deploy, switch-instance --force) termina com uma linha de maquina, a ultima do stdout. Sem a variavel a linha nao sai (client antigo le o envelope JSON pela ultima linha); o exit code vale sempre.

RUNNER_RESULT {"deploy":"ok","pos_deploy":[{"passo":"secrets_push","erro":"sem_token","mensagem":"..."}]}
Campo Conteudo
deploy ok ou falhou
pos_deploy[] As pendencias. Vazio quando nada falhou depois do deploy
pos_deploy[].passo O passo que falhou: secrets_push, manifesto_repo_divergiu, …
pos_deploy[].erro sem_token, token_recusado ou outro
pos_deploy[].mensagem Texto para o operador (app e comando de conserto). Nunca carrega segredo
pos_deploy[].dados Opcional e aditivo. Em manifesto_repo_divergiu: {"commit", "campos"} (os nomes das chaves que mudaram)

A pendencia manifesto_repo_divergiu (2.178.0+) sai quando o repositorio mudou o manifesto e o deploy usou o manifesto fixado no servidor:

{"passo": "manifesto_repo_divergiu", "erro": "outro",
 "mensagem": "implantado com o manifesto fixado (3a51b693); … — a mudança do repo não foi aplicada. Confira com `runner manifest status`",
 "dados": {"commit": "d4e5f6", "campos": ["healthcheck"]}}

Ver Quando o repositorio muda o manifesto.

Logs

# Ver historico
runner logs history --app /data/apps/meu-app

# Ver log do ultimo deploy
runner logs tail --app /data/apps/meu-app

Estrutura Apos Deploy

/data/apps/meu-app/
├── .deploy.yml
├── .env
├── .secrets
├── .runner/
│   └── secrets.enc       # Secrets encriptados (se ckey configurada)
└── src/
    └── production/
        ├── v1.0.0/
        ├── v1.0.1/
        └── current -> v1.0.1

Secrets Encriptados

Se a app foi registrada com --ckey, o Runner encripta .env e .secrets apos cada deploy e persiste como .runner/secrets.enc no repositorio. Isso permite recuperacao zero-touch em caso de perda do servidor.

Consulte o guia Secrets e Encriptacao para detalhes completos.

Multiplos alvos e a fronteira do projeto (v2.80.0+)

<app> nao e um nome exato: e uma query, casada por segmento. runner deploy ccs alcanca ccs/sys, ccs/front e ccs/worker — replicar o comando nos sistemas de um projeto e o desenho do runner, nao um acidente.

Situacao Comportamento
1 alvo Executa direto, sem cerimonia
N alvos, mesmo projeto Lista os alvos e pede confirmacao. -y, --yes dispensa
N alvos, projetos distintos Terminal: pergunta. Cron/CI (sem TTY): aborta com exit != 0. Nenhuma flag autoriza

O "projeto" aqui e a raiz antes do primeiro hifen: ccs-front e ccs-sys sao o mesmo projeto (ccs); ccs-front e nfse-front nao sao.

Por que `--yes` nao autoriza atravessar

--yes e passado por habito, em script e cron. Se valesse para o cruzamento, o gate morreria exatamente onde ninguem esta olhando. Uma query casa por segmento e deveria cair dentro de uma familia — atravessar e anomalia, nao intencao.

ERROR deploy atravessa 2 projetos (ccs, nfse) em 4 alvos:
  - ccs/sys
  - ccs/front
  - nfse/sys
  - nfse/front

Uma query casa por segmento e deveria cair dentro de UMA família — atravessar aqui é anomalia, não intenção, e por isso nenhuma flag autoriza.
Se quis mesmo atingir vários projetos, o comando é `--all`.
Para um alvo só: -x

Em terminal interativo o cruzamento pergunta em vez de abortar. Em cron/CI (sem TTY) aborta com exit != 0.

Saidas:

  • -x, --exact — desliga a query e mira um alvo so
  • --all — a forma explicita de operar em massa (tem gate proprio)
By Borlot.com.br on 03/06/2026