Referencia .deploy.yml

O .deploy.yml e o arquivo de configuracao central que define como sua aplicacao sera deployada.

Modos de Deploy (v1.2.3+)

O Runner opera em dois modos exclusivos, determinados pela presenca de image: ou build::

Tem image: ?           -> MODO IMAGE (sem build, monta source via volume)
Tem build: ?           -> MODO BUILD (docker build com Dockerfile do repo)
Ambos?                 -> Erro
Nenhum?                -> Erro

Modo IMAGE

Usa uma imagem pre-construida do registry. O Runner monta o codigo fonte via volume.

project: meu-site
image: caddy:2-alpine
port: 80
  • NAO faz build
  • Monta source: src/{instance}/current:/app:ro
  • Auto-mount de data/, logs/, keys/
  • Health check: curl/wget

Modo BUILD

Faz docker build usando o Dockerfile que esta no repositorio.

project: meu-app
build:
  dockerfile: Dockerfile
  context: .
port: 80
  • Faz docker build com o Dockerfile do repo
  • Codigo embedded na imagem (COPY no Dockerfile)
  • Auto-mount de data/, logs/, keys/ (NAO monta source)
  • Health check: bash /dev/tcp

Tabela Comparativa

Modo IMAGE Modo BUILD
Config image: xxx build:
Build? NAO SIM (docker build)
Codigo Volume mount (:ro) Embedded na imagem
Dockerfile no repo Nao precisa OBRIGATORIO
Source mount src/inst/current:/app:ro Nenhum
Persistent volumes data/, logs/, keys/ data/, logs/, keys/
Health check curl/wget bash /dev/tcp

Retrocompatibilidade

Campos legacy usados em manifestos v1 (relacionados ao modo de deploy ou ao "tipo" da app) ainda sao aceitos pelo parser apenas para nao quebrar manifestos antigos — emitem warning e nao influenciam o modo escolhido. O modo deriva exclusivamente da presenca de image: (modo IMAGE) ou da presenca de build: (modo BUILD).

Para a tabela completa de campos deprecados e seus substitutos canonicos (incluindo o alias dockerfile em image_mode, o campo app_type e o type: de topo), consulte Recursos Deprecados.

Sintaxe inline em `environment` / `secrets` / `build_args`

Valores em environment:, secrets: e build.args: aceitam uma micro-sintaxe processada pelo modulo env_wizard do runner deploy. O wizard resolve cada placeholder na primeira execucao, persiste o valor final em .env/.secrets, e a partir dai o valor fica literal — sem reprocessamento em deploys subsequentes.

Sintaxe Significado Resolvido por
valor literal Valor hardcoded, copiado tal qual parse direto
"{{::Label}}" Pergunta no terminal na 1a execucao runner deploy (env_wizard)
"{{::Label?default}}" Pergunta com default se a resposta vier vazia runner deploy (env_wizard)
"{RANDOM}" Gera 32 chars [A-Za-z0-9] uma vez runner deploy (env_wizard)
"{GENERATE:hex:N}" Gera N chars hexadecimais uma vez runner deploy (env_wizard)
${ENV_VAR} Variavel compose-spec resolvida em multiplas fontes (v2.26.9+) runner deploy em build.args, e runner wizard em environment:

Importante: essa sintaxe e processada somente pelo runner deploy (modulo env_wizard) na primeira execucao. Edicao direta do .deploy.yml fora do wizard exige rodar runner deploy para reprocessar — caso contrario, o valor fica literal no .env e o placeholder nunca e expandido.

`${VAR}` em `build.args` — v2.26.9+ semantics

build.args tem ciclo de vida diferente de environment:/secrets: porque sao consumidos em build time (docker build) e nao em runtime. v2.26.9 + v2.26.10 trazem 2 camadas pra cobrir todos os fluxos de origem do valor:

1. Wizard-time bake-in (runner wizard --from-compose):

Quando o shell env tem a variavel setada, o wizard substitui no manifest:

export VITE_SUPABASE_URL=https://x.supabase.co
runner wizard --from-compose --path .
# manifest gerado: args: { VITE_SUPABASE_URL: "https://x.supabase.co" }

Se o shell env nao tem a variavel, o wizard preserva o literal ${VAR} no manifest (v2.26.10+) — antes virava string vazia, causando silent miscompile em bundles Vite/Next/CRA.

2. Deploy-time runtime expansion (runner deploy):

No momento do docker build, o runner re-processa cada valor de build.args resolvendo ${VAR} contra (em ordem de precedencia):

  1. .secrets do app (runner secrets set)
  2. .env do app (runner env set)
  3. Shell env de quem rodou o runner deploy

Isso permite o fluxo post-wizard: gerar manifest, registrar app, setar valores via runner env set, e ter os valores no build sem precisar re-rodar wizard:

# Apos wizard, manifest tem `args: { VITE_SUPABASE_URL: "${VITE_SUPABASE_URL}" }` (literal)
runner env set <app> --key VITE_SUPABASE_URL --value "https://x.supabase.co"
runner deploy <app> --force   # --force pra invalidar docker layer cache
# docker build --build-arg VITE_SUPABASE_URL=https://x.supabase.co ✓

Precedencia completa em build.args:

  1. {{::Var}} (runner-specific syntax) — resolve via .env/.secrets/prompts.
  2. ${VAR} (compose-spec) — resolve via .secrets > .env > shell env.
  3. Literal — passthrough.

Exemplo:

environment:
  FLASK_ENV: production                       # literal
  BASE_DOMAIN: "{{::Domain?app.example.com}}" # prompt com default
  REGION: "${AWS_REGION}"                     # do shell
secrets:
  DB_PASSWORD: "{RANDOM}"                     # gerada uma vez
  JWT_SECRET: "{GENERATE:hex:64}"             # 64 chars hex
  SMTP_PASSWORD: "{{::SMTP password}}"        # prompt

Estrutura Completa

# ============================================
# IDENTIFICACAO
# ============================================
project: meu-projeto          # Nome do projeto (obrigatorio)
system: front                 # Descritor livre (front, sys, api, worker, scanner, ...) — cosmetico desde v2

# ============================================
# MODO DE DEPLOY (escolher um)
# ============================================
# Opcao A: Modo IMAGE (imagem pronta)
image: registry.local/base-sys:1.0

# Opcao B: Modo BUILD (Dockerfile do repo)
build:
  dockerfile: Dockerfile      # Default: "Dockerfile"
  context: .                  # Default: "."

port: 8000                             # Porta interna

# ============================================
# PORTAS (v2.0.1 — deploy sem Traefik)
# ============================================
ports:
  - "80:80"                    # host:container
  - "10.108.0.4:80:80"        # ip:host:container (bind na VPC)

# ============================================
# CONFIG TEMPLATES (v2.0.0 — Helm-style)
# ============================================
config_templates:
  values_dir: config           # Default: "config"
  templates_dir: templates     # Default: "templates"

# ============================================
# REDES E VOLUMES
# ============================================
networks:
  - public                    # Network do Traefik (obrigatoria)
  - mysql                     # Se usar MySQL
  - redis                     # Se usar Redis

volumes:
  - logs:/app/logs:rw         # Formato: nome:mount:mode
  - certs:/app/certs:ro

# ============================================
# VARIAVEIS DE AMBIENTE
# ============================================
environment:
  PYTHONPATH: /app/src
  FLASK_ENV: production
  DEBUG: "false"
  LOG_LEVEL: info

# ============================================
# HEALTH CHECK (tres modos mutuamente exclusivos)
# ============================================
# Prioridade: cmd > tcp > path
#
# Modo HTTP (default) — GET no path indicado
healthcheck:
  path: /health               # Endpoint HTTP
  interval: 10s
  timeout: 5s
  retries: 3
#
# Modo TCP — sondagem de porta (servicos nao-HTTP)
# healthcheck:
#   tcp: 25                   # Considera healthy se a porta aceita conexao
#
# Modo CMD — exec dentro do container (use o binario que voce ja tem)
# healthcheck:
#   cmd: ["/app/bin/healthcheck", "--strict"]
#
# Modo EXIT — CLI tools/scanners (sem container permanente)
# healthcheck:
#   mode: exit
#
# Importante: se o seu Dockerfile ja declara HEALTHCHECK, o runner respeita
# essa declaracao e nao injeta nada por cima.

# ============================================
# GITHUB (DEPRECATED na v2.16.0+)
# ============================================
# A partir da v2.16.0, repo/branch vivem no state file (manifesto do runner)
# em /opt/runner/state/{app}.yml. O `runner add --repo X --branch Y` salva
# direto no state — nao precisa do `github:` no .deploy.yml.
#
# Migration silenciosa: apps existentes com `github:` no .deploy.yml tem o
# valor copiado pro state na primeira execucao, com warning de deprecated.
# A leitura compat continua funcionando, mas o campo sera removido em uma
# major futura.
#
# github:
#   repo: usuario/meu-repo      # DEPRECATED — agora vem de runner add --repo
#   user: usuario               # DEPRECATED

# ============================================
# VERSAO
# ============================================
version:
  source: file                # file | git-tag | git-commit
  file: package.json          # Arquivo que contem a versao
  field: version              # Campo dentro do arquivo

# ============================================
# DEPENDENCIAS ENTRE APPS (opcional)
# ============================================
# Lista de outros projetos registrados que devem estar deployados (e
# saudaveis) antes deste. Consumido pelo `runner deploy --all` para
# topologicamente ordenar a sequencia. Cycles sao detectados e abortam
# o deploy. Deps que apontam para projetos nao registrados localmente
# emitem warning mas nao bloqueiam.
depends_on:
  - inboxer-mta               # MTA precisa estar healthy antes do SYS subir
  - inboxer-redis

# ============================================
# ENV SYNC (v2.23.18 — controle de soberania)
# ============================================
# Controla se .env/.secrets sao regenerados a partir do secrets.enc
# remoto quando o hash muda durante deploy.
#
# remote (default): remoto soberano — hash diferente = regenera .env/.secrets
# skip: local soberano — nunca sobrescreve .env/.secrets por mudanca remota
env_sync: remote

# ============================================
# INSTANCIAS
# ============================================
instances:
  production:
    domain: app.projeto.com.br
    source:
      type: dist              # Usa branch dist
    keep_versions: 3          # Manter ultimas 3 versoes
    canary:                   # Configuracao de canary (v1.2.0)
      enabled: true
      auto_promote:
        enabled: true
        initial_weight: 10
        increment: 10
        interval: 5m
        final_weight: 100

  staging:
    domain: staging.projeto.com.br
    source:
      type: branch
      branch: develop         # Usa branch develop
    keep_versions: 2

  release:
    domain: release.projeto.com.br
    source:
      type: tag               # Usa tags Git (v1.2.0)
      tag_pattern: "v*"       # Padrao glob para filtrar tags

  hotfix:
    source:
      type: local             # Usa diretorio local (v1.2.0)
      path: /builds/hotfix/

  canary:
    source:
      type: image             # Usa imagem Docker pronta (v1.2.0)
      image: ghcr.io/org/app:canary

  pr:
    domain_pattern: "pr-${PR_NUMBER}.staging.projeto.com.br"
    source:
      type: pr                # Usa refs/pull/N/head
    ephemeral: true           # Instancia temporaria
    ttl_hours: 48             # Auto-destruicao apos 48h

  mcp:
    domain: app.projeto.com.br
    path_prefix: /mcp         # Co-hospeda em app.projeto.com.br/mcp (v2.22.0+)
    source:
      type: dist
    keep_versions: 3

# ============================================
# STAGING (configuracao global)
# ============================================
staging:
  enabled: true
  domain_template: "pr-${PR_NUMBER}.staging.${DOMAIN}"
  container_template: "staging_${PROJECT}_pr${PR_NUMBER}"
  max_instances: 3
  ttl_hours: 48

# ============================================
# NOTIFICACOES
# ============================================
notify:
  channels:
    - integration: telegram   # Referencia config.yml
      chat_id: "-123456789"
      events:
        - deploy_started
        - deploy_success
        - deploy_failure

    - integration: flare
      events:
        - deploy_success

# ============================================
# ROUTING MODE (obrigatorio para canary)
# ============================================
routing_mode: traefik_dynamic  # traefik_dynamic | traefik_labels | none

Campos Detalhados

Identificacao

Campo Tipo Obrigatorio Descricao
project string Sim Nome do projeto. Usado no container name, Traefik config, labels e diretorio
system string Nao Identificador do subtipo (front, sys, api, worker). Usado como label de display e no atalho CLI runner deploy project/system
type string Nao DEPRECATED — cosmetico; o modo agora deriva da presenca de build: ou image:. Mantido para compatibilidade com manifestos antigos.

O campo system e cosmético — nao afeta build, routing ou container name. Serve para:

  • Atalho CLI: runner deploy sweepapex/front (resolve project=sweepapex + system=front)
  • Display nos logs: sweepapex/front deployed
  • Organizacao quando um projeto tem multiplos repos (front, sys, worker)

Modo de Deploy

Campo Tipo Default Descricao
image string - Imagem Docker (define modo IMAGE)
build object - Config de build (define modo BUILD)
build.dockerfile string Dockerfile Caminho do Dockerfile
build.context string . Contexto de build
build.args map {} Build args injetados como --build-arg KEY=VAL. v2.20.0+; v2.21.0+ suporta interpolação {{::Var}} resolvida de .env/.secrets
port integer - Porta interna do container
image_mode string bind-mount Deprecated — prefira image: ou build:. Aceita bind-mount, self-contained, e o alias dockerfile (que vira self-contained com warning de depreciacao).

Regra: image e build sao mutuamente exclusivos. Usar apenas um.

Build Args (v2.20.0+)

build.args permite injetar variáveis em build time via --build-arg. Em v2.21.0+, valores suportam interpolação {{::Var}} resolvida de .env e .secrets da app.

build:
  context: .
  dockerfile: Dockerfile
  args:
    BUILD_ENV: production                          # literal
    VITE_API_URL: "{{::API_URL}}"                  # interpola de .env/.secrets (v2.21.0+)
    VITE_DEFAULT_DOMAIN: "{{::DOMAIN?app.example.com}}"   # com default

Order de resolução: .secrets primeiro (mais específico), .env fallback. Suporta {{::Key?default}}. Se a var não resolve, erro instrutivo aponta pra runner env set.

Ports (v2.0.1)

Mapeamento de portas do host para o container. Usado em deploys sem Traefik (servidores backend acessiveis via proxy externo pela VPC).

ports:
  - "80:80"                    # Todas as interfaces
  - "10.108.0.4:80:80"        # Bind no IP da VPC (recomendado)
  - "127.0.0.1:8080:80"       # Bind no localhost
Formato Exemplo Resultado
host:container "80:80" Todas as interfaces
ip:host:container "10.108.0.4:80:80" Bind no IP especifico

Quando ports esta configurado e a instancia nao tem domain, o runner:

  • Pula a geracao de Traefik config
  • Para o container antigo antes de iniciar o novo (stop-then-start, ~5s downtime)
  • Mapeia -p no docker run

Config Templates (v2.0.0)

Configuracao opcional para renderizacao de config files Helm-style via Tera.

config_templates:
  values_dir: config           # Default: "config"
  templates_dir: templates     # Default: "templates"

Quando presente e o repo tem templates/*.tpl, o runner renderiza templates automaticamente durante o deploy (Phase 1.3). Ver `runner config` para gerenciar valores.

Networks

Lista de redes Docker para conectar o container.

networks:
  - public    # Obrigatoria para Traefik
  - mysql     # Se usar MySQL
  - redis     # Se usar Redis
  - mongo     # Se usar MongoDB

Project-scoping de networks user-defined (v2.26.3+) — quando runner wizard --from-compose gera multiplos manifests do mesmo project_prefix, networks user-defined (que nao sao reserved/shared) ganham prefixo automaticamente:

Compose Manifest gerado Por que
networks: [meet] (project_prefix=meet) networks: [meet_meet] App-local, compartilhada entre apps do mesmo prefix
networks: [mysql] networks: [mysql] Shared infra, mesma network entre projetos diferentes
networks: [public] networks: [public] Reservada (Traefik)

Network reservadas (passam unchanged): public, bridge, host, none, default. Network compartilhadas pre-instaladas (passam unchanged): mysql, redis, postgres, postgresql, mongo, mongodb, elasticsearch, clickhouse, rabbitmq, kafka, nats.

Wizard tambem popula network_aliases: com o service-name original em cada network project-scoped. Resultado: sibling apps resolvem-se por nome curto (livekit:7880), igual docker compose.

Hosts (v2.20.0+)

DNS aliases injetados como --add-host KEY:VAL no docker run. Map (não array) — last-wins em duplicatas.

hosts:
  host.docker.internal: host-gateway   # resolve em Linux Docker
  internal-api: 10.0.0.5
  cache-server: 10.0.0.10

Útil pra:

  • host.docker.internal em Linux Docker (não funciona por default sem --add-host=host.docker.internal:host-gateway)
  • Aliases pra serviços internos sem depender de DNS externo
  • Override temporário de hosts pra debug

Volumes

Lista de volumes no formato host:container:mode (bind) ou nome:mount:mode (named).

volumes:
  - logs:/app/logs:rw
  - certs:/app/certs:ro
  - data:/app/data:rw
  - ./livekit.prod.yaml:/etc/livekit.yaml:ro    # bind relativo

Resolucao de path relativo (v2.26.2+) — paths relativos como ./X agora seguem semantica docker-compose:

Cenario Resolve para
Arquivo X existe em src/<instance>/<version>/X (vindo do repo) src/<instance>/<version>/X
Caso contrario <app_dir>/X (state runtime: ./data, ./logs, etc.)

Pre-v2.26.2, todos os relatives bateram em <app_dir>/X o que quebrou config-file binds (livekit.prod.yaml, nginx.conf, etc.) — o arquivo nunca existia ali.

Source Dir

source_dir: .       # default explicito = repo root (v2.26.2+)
source_dir: dist    # subdir do repo (build output, etc.)
source_dir: auto    # heuristica antiga (tenta dist/build/out/public/guides/content/docs)

Default (v2.26.2+) — quando source_dir esta ausente ou e null, o runner usa pull/ inteiro como source. Matches docker-compose (relative paths in volumes: resolvem ao dir do compose file).

source_dir: auto — opt-in pra heuristica antiga. Util para projetos front-static cujo build vai pra dist/ ou similar e o operador prefere nao declarar explicitamente.

Pre-v2.26.2, image-mode apps com source_dir: null rodavam auto-detect automaticamente e podiam escolher subdir errado (e.g. docs/ em repo com markdown na pasta).

Environment

HashMap de variaveis de ambiente. Suporta valores fixos, gerados e prompts interativos (Env Wizard).

environment:
  FLASK_ENV: production                          # valor fixo
  BASE_DOMAIN: "{{::Domain?projeto.com.br}}"     # prompt com default
  DB_NAME: "{{::Database name}}"                 # prompt sem default

Precedencia (v2.26.3+) — chaves presentes em .env ou .secrets (estado local do operador) sobrescrevem environment: do manifest. Manifest vale como default; operador override em prod ganha. Use runner env set <app> <KEY> <VALUE> pra setar overrides sem editar/commitar o .deploy.yml.

Pre-v2.26.3, manifest sempre ganhava — runner env set era no-op para qualquer chave declarada no manifest.

Secrets

HashMap de variaveis sensiveis. Escritas em .secrets (permissao 600) em vez de .env.

secrets:
  DB_PASSWORD: "{RANDOM}"                        # gera automaticamente
  JWT_SECRET: "${GENERATE:hex:64}"               # hex 64 chars
  SMTP_PASSWORD: "{{::SMTP Password}}"           # prompt ao operador

Consulte o guia completo de classificação automática de env/secrets para sintaxe detalhada e exemplos.

Env Sync (v2.23.18)

Controla se .env/.secrets sao regenerados a partir do secrets.enc remoto quando o hash muda.

env_sync: remote   # default — remoto soberano
env_sync: skip     # local soberano
Campo Tipo Default Descricao
env_sync string remote remote: regenera .env/.secrets se secrets.enc remoto mudou. skip: nunca sobrescreve locais.

Quando omitido, herda o valor de env_sync_default do config.yml global.

Resources (v2.24.0)

Limites de recursos via flags do docker run. Todos os campos opcionais — ausente = sem flag, comportamento idêntico a versões anteriores.

resources:
  memory: 512m              # --memory
  memory_swap: 1g           # --memory-swap
  memory_reservation: 256m  # --memory-reservation (soft limit)
  cpus: "1.5"               # --cpus
  cpu_shares: 1024          # --cpu-shares (peso relativo)
  cpuset: "0-1"             # --cpuset-cpus (pinning)
  pids_limit: 100           # --pids-limit (anti fork-bomb)
  shm_size: 64m             # --shm-size (default 64m; chrome/puppeteer geralmente precisa 256m+)
  ulimits:
    nofile: 65536           # --ulimit nofile=65536
    nproc: 4096             # --ulimit nproc=4096

Security (v2.24.0)

Hardening via flags do docker run. Defaults preservam comportamento atual.

security:
  user: "1000:1000"           # --user UID[:GID]. Recomenda nao-root.
  read_only: true             # --read-only (root FS imutavel; combine com tmpfs)
  no_new_privileges: true     # --security-opt no-new-privileges:true
  cap_add: [NET_ADMIN]        # --cap-add (Linux capabilities)
  cap_drop: [ALL]             # --cap-drop
  security_opts:              # --security-opt (raw)
    - "seccomp=unconfined"
    - "apparmor=docker-default"

Logging (v2.24.0)

Driver de log e opções (rotação, etc).

logging:
  driver: json-file           # --log-driver (json-file | journald | syslog | local | none)
  options:                    # --log-opt key=value (em ordem alfabetica)
    max-size: 10m
    max-file: "3"

Recomenda-se max-size: 10m, max-file: 3 em produção pra prevenir explosão de disco.

Lifecycle (v2.24.0)

Controle de inicialização, entrypoint, comando e shutdown.

init: false                   # --init (default false). PID 1 = tini para reap de zombies.
entrypoint: ["/app/start", "--config", "/etc/app.yml"]  # --entrypoint + args após image
command: ["--bind", "0.0.0.0", "--port", "8080"]        # CMD (args após entrypoint). v2.26.3+
stop_signal: SIGTERM          # --stop-signal (default SIGTERM)
stop_grace_period: 30s        # --stop-timeout em segundos antes de SIGKILL
pull_policy: missing          # --pull (always | missing | never). Ausente = daemon default.

command: (v2.26.3+) — override do CMD da imagem. Aceita array ou shell-string (splittada em whitespace). Quando ambos entrypoint: e command: estao setados, o runner monta a chamada como docker run --entrypoint <EP> IMAGE <CMD...>. Wizard --from-compose traduz compose.services.X.command automaticamente. Paths com espaco exigem array form (sem suporte a quoted segments na string form).

# Array form (preferido pra paths com espaco)
command:
  - --config
  - /etc/livekit.yaml
  - --bind
  - 0.0.0.0

# Shell form (split por whitespace)
command: --config /etc/livekit.yaml --bind 0.0.0.0

Network avançado (v2.24.0)

hostname: app-prod-1                # --hostname interno do container
dns: ["1.1.1.1", "8.8.8.8"]         # --dns servidores customizados
expose: [8080, 9090]                # --expose (portas internas sem publish)
network_aliases:
  public: [app-prod, legacy]        # --network-alias (advisory em multi-network)

Storage / Hardware (v2.24.0)

tmpfs: ["/tmp:size=64m", "/run"]    # --tmpfs (otimo com read_only: true)
devices: ["/dev/snd", "/dev/dri"]   # --device (passthrough de dispositivos)
gpus: "all"                         # --gpus (requer nvidia-container-toolkit)
platform: "linux/arm64"             # --platform (cross-arch testing)

Health Check

O healthcheck opera em 3 camadas (cascata de decisao):

  1. Layer 1: HEALTHCHECK no Dockerfile (sempre vence)
  2. Layer 2: mode avancado (v2.27.0+) — runner faz probe de fora
  3. Layer 3: path/tcp/cmd — probe dentro do container
Campo Tipo Default Descricao
path string /health Modo HTTP (Layer 3): endpoint testado via curl/wget (image mode) ou bash /dev/tcp (build mode). Deve comecar com /.
tcp integer - Modo TCP (Layer 3): porta interna para sondagem. Use para servicos nao-HTTP (SMTP, Redis, ...).
cmd array of strings - Modo CMD (Layer 3): comando exec rodado dentro do container. Use quando a imagem ja tem um binario de healthcheck. Ex: cmd: ["/app/bin/check", "--strict"].
mode string - Estrategia avancada (Layer 2). Valores: exit, external_http, external_tcp, container_state, skip_check. Ver tabela abaixo.
stable_seconds integer 10 Usado com mode: container_state. Janela de estabilidade (container deve ficar Running sem OOM).
start_period string 30s Grace period antes do Docker/runner comecar a rodar checks. Aumente para containers lentos (multi-stage builds, JVMs).
interval string 10s Intervalo entre checks
timeout string 5s Timeout do check
retries integer 3 Tentativas antes de unhealthy (usado em Layer 3, ignorado em Layer 2)

Modos Avancados (Layer 2) — v2.27.0+

Modo Descricao Uso
exit CLI tools que nao ficam rodando. fetch --deploy skipa, runner deploy valida com docker run --rm. Scanners, CLI tools
external_http Runner faz HTTP GET ao http://<container_ip>:<port><path> do host. Funciona em qualquer imagem (scratch, distroless). Containers sem ferramentas internas
external_tcp Runner faz TCP connect ao <container_ip>:<tcp> do host. Requer campo tcp: preenchido. Servicos sem HTTP (Redis, MySQL)
container_state Runner observa docker inspect — container saudavel apos stable_seconds com Running==true, sem OOM, sem RestartCount incrementado. Apps que nao expõem nada inspecionavel
skip_check Escape hatch: runner declara OK sem validacao. Emite evento HealthSkipped (Warning) no canal de notificacoes. Risco aceitavel explicitamente

Heranca de HEALTHCHECK do Dockerfile: se a imagem base (ou a imagem buildada pelo seu Dockerfile) ja declara um HEALTHCHECK, o runner respeita essa declaracao e nao injeta nenhum --health-cmd por cima (Layer 1 sempre vence). Isso permite que projetos com Dockerfile customizado tenham controle total da estrategia de health. Use runner deploy -V para ver qual comando sera efetivamente usado.

Depends On

Lista de projetos que devem ser deployados antes deste quando voce roda runner deploy --all. Os nomes referem-se ao campo project: de outros apps registrados localmente.

depends_on:
  - inboxer-mta
  - inboxer-redis

Comportamento:

  • O runner faz topological sort e deploya na ordem correta.
  • Cycles (a -> b -> a) sao detectados e abortam o --all com mensagem listando os projetos envolvidos.
  • Deps que apontam para projetos nao registrados localmente emitem warning e sao silenciosamente ignoradas — util quando o dep roda em outro servidor.
  • Se uma dep falhar durante o --all, seus dependentes sao automaticamente pulados (com log) ao inves de tentar deployar sem a base.

Version

Campo Tipo Descricao
source string Fonte da versao
file string Arquivo com a versao
field string Campo no arquivo

Valores de source:

  • file - Extrai de arquivo (package.json, version.json)
  • git-tag - Usa tag Git mais recente
  • git-commit - Usa hash do commit

Instances

Cada instancia define um ambiente de deploy.

Campo Tipo Descricao
domain string Dominio da instancia
domain_pattern string Padrao de dominio (para PRs)
path_prefix string URL prefix para co-hospedar no mesmo domain de outro app (v2.22.0+)
source.type string Tipo de fonte
source.branch string Nome da branch (se type=branch)
source.tag_pattern string Padrao de tags (se type=tag)
source.path string Caminho local (se type=local)
source.image string Imagem Docker (se type=image)
keep_versions integer Quantas versoes manter
ephemeral boolean Se e instancia temporaria
ttl_hours integer TTL em horas (para ephemeral)
environment_overrides map Variaveis de ambiente especificas desta instancia (sobrescreve environment)
canary object Configuracao de canary
config object Resolucao de secrets por instancia (v2.44.0+) — ver Config por Instancia

Tipos de source (v1.2.0):

Tipo Descricao Campos
dist Branch dist (artifacts compilados) -
branch Branch especifica branch
pr Pull Request (refs/pull/N/head) -
tag Tag Git especifica tag_pattern
local Diretorio local path
image Imagem Docker pronta image

Path Prefix (v2.22.0+)

Permite co-hospedar dois apps no mesmo domain, cada um servindo um sub-caminho. Quando path_prefix: esta definido, o runner gera a regra Traefik:

Host(`{domain}`) && PathPrefix(`{path_prefix}`)

com priority: 100 — vence o router catch-all do app vizinho que serve a raiz do mesmo host.

instances:
  production:
    domain: wizard.runnerci.com
    path_prefix: /mcp
    source:
      type: dist
    keep_versions: 3

Caso de uso real: o frontend wizard serve wizard.runnerci.com/ e o servidor MCP wizard-mcp serve wizard.runnerci.com/mcp/* — ambos como containers separados sob o mesmo dominio.

Comportamento:

  • Campo vazio ou omitido → regra Traefik fica apenas Host() (comportamento legacy, sem priority)
  • Whitespace-only e tratado como ausente
  • Todos os tres caminhos de geracao Traefik (single-version, canary, snippet externo) honram o prefix

Config por Instancia (v2.44.0+)

Opcional. So e necessario quando o bundle de secrets da instancia nao segue a convencao de nome, ou (fora de escopo nesta versao) para vault.

instances:
  production:
    domain: app.com.br
    config:
      source: bundle                     # default (zero-dependencia)
      bundle: secrets.production.enc     # opcional; omitido = convencao
Campo Tipo Default Descricao
source string bundle Origem da resolucao de secrets desta instancia
bundle string convencao (secrets.<instancia>.enc) Path do arquivo .enc, se diferente da convencao

Sem esse bloco, o runner resolve por convencao de nome — nao precisa declarar nada. A ordem completa de failover (bundle explicito → secrets.<instancia>.encsecrets.enc legado) e detalhada em Secrets por Instancia.

source: vault existe no schema mas ainda retorna erro loud no deploy — resolucao via vault esta fora de escopo nesta versao. Use source: bundle.

Estruture os arquivos por-instancia com `runner set-ckey`.

Canary Config (v1.2.0)

Configuracao de canary deployment por instancia:

instances:
  production:
    canary:
      enabled: true
      auto_promote:
        enabled: true
        initial_weight: 10       # Peso inicial do canary
        increment: 10            # Incremento por step
        interval: 5m             # Intervalo entre steps
        final_weight: 100        # Peso final (promocao completa)
        health_check_before: true
      abort_on:
        - health_check_failed
        - error_rate_above:5%
        - latency_p99_above:500ms

Campos de auto_promote:

Campo Tipo Default Descricao
enabled boolean false Habilitar promocao automatica
initial_weight integer 10 Peso inicial
increment integer 10 Incremento por step
interval string 5m Intervalo entre steps
final_weight integer 100 Peso final
health_check_before boolean true Verificar health antes de cada step

Condicoes de abort:

  • health_check_failed - Health check falhou
  • error_rate_above:X% - Taxa de erro acima de X%
  • latency_p99_above:Xms - Latencia p99 acima de X ms

Instance Depends_on (v2.21.0+)

Cross-app health checks por instance: app A só inicia depois que app B está pronto. Diferente do top-level depends_on: (que ordena runner deploy --all), o instances.<inst>.depends_on: é checado em cada start_container e bloqueia o spin-up até o dep estar healthy.

instances:
  production:
    domain: app.example.com
    source: { type: branch, branch: main }
    keep_versions: 3
    depends_on:
      - app: meu-livekit
        condition: healthy           # healthy | started
        timeout: 300s                # opcional, default 300s
      - app: meu-redis
        instance: production         # opcional, default = mesma instance
        condition: started
Campo Tipo Default Descricao
app string - Slug do app dependência (obrigatório)
instance string mesma instance Instance específica do dep
condition enum healthy healthy (Health.Status=healthy) ou started (running)
timeout string "300s" Max wait — 300s / 5m / 1h

Comportamento: runner polla docker inspect por cada dep até condition met OR timeout. Bypass via --insecure.

Erro de timeout:

ERROR dependency_timeout: meu-livekit/production not ready after 300s (condition=Healthy)

Routing Mode

Valor Canary Descricao
traefik_dynamic Sim Arquivos YAML dinamicos (obrigatorio para canary)
traefik_labels Nao Labels nos containers
none Nao Sem routing (workers)

Importante: Se canary.enabled: true, routing_mode DEVE ser traefik_dynamic.

Staging

Configuracao global de staging.

Campo Tipo Default Descricao
enabled boolean false Habilitar staging
domain_template string - Template de dominio
container_template string - Template de container
max_instances integer 3 Max stagings por projeto
ttl_hours integer 48 TTL padrao

Variaveis de template:

  • ${PR_NUMBER} - Numero da PR
  • ${PROJECT} - Nome do projeto
  • ${BRANCH} - Nome da branch
  • ${DOMAIN} - Dominio base
  • ${INSTANCE} - Nome da instancia (v2.0.0)
  • ${DATETIME} - Timestamp (YYYYmmdd-HHMMSS)
  • ${HASH} - 6 caracteres de UUID
  • ${RANDOM} - 6 caracteres alfanumericos

Notify (v1.2.0)

Configuracao de notificacoes por aplicacao:

notify:
  channels:
    - integration: telegram      # Referencia integrations do config.yml
      chat_id: "-123456789"      # Override per-app
      events:
        - deploy_started
        - deploy_success
        - deploy_failure

    - integration: flare
      events:
        - deploy_success

    - integration: slack
      channel: "#deploys"
      events: all                # Todos os eventos

Eventos disponiveis:

  • deploy_started
  • deploy_success
  • deploy_failure
  • rollback_started
  • rollback_success
  • canary_weight_changed
  • canary_promoted
  • instance_destroyed
  • health_check_failed
  • all (todos os eventos)

Exemplos por Tipo

Frontend estatico — Modo IMAGE (Caddy)

project: meu-app
image: caddy:2-alpine
port: 80

networks:
  - public

healthcheck:
  path: /
  interval: 30s

github:
  repo: usuario/meu-app-front

version:
  source: file
  file: package.json
  field: version

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist
    keep_versions: 3

Backend Flask — Modo BUILD com Canary

project: meu-api
build:
  dockerfile: Dockerfile
port: 8000
routing_mode: traefik_dynamic    # Obrigatorio para canary

networks:
  - public
  - mysql
  - redis

environment:
  PYTHONPATH: /app/src
  FLASK_ENV: production

healthcheck:
  path: /health
  interval: 10s
  timeout: 5s
  retries: 3

github:
  repo: usuario/meu-api-sys

version:
  source: file
  file: version.json
  field: version

instances:
  production:
    domain: sys.meusite.com.br
    source:
      type: dist
    keep_versions: 3
    canary:
      enabled: true
      auto_promote:
        enabled: true
        initial_weight: 10
        increment: 10
        interval: 5m
      abort_on:
        - health_check_failed
        - error_rate_above:5%

Documentacao — Modo IMAGE (PimDocs)

project: runner-docs
image: leonardoborlot/pimdocs:latest
port: 4321

networks:
  - public

healthcheck:
  path: /
  interval: 30s

instances:
  production:
    domain: docs.runner.ccs.systems
    source:
      type: dist

App PHP — Modo BUILD com multi-stage

project: meu-laravel
build:
  dockerfile: docker/Dockerfile.prod
  context: .
port: 80

networks:
  - public
  - mysql

volumes:
  - storage:/app/storage:rw

healthcheck:
  path: /health

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist
    keep_versions: 3

Deploy de Tags (Releases)

project: meu-app
port: 8000
networks:
  - public

instances:
  release:
    domain: app.meusite.com.br
    source:
      type: tag
      tag_pattern: "v*"          # v1.0.0, v1.1.0, etc
    keep_versions: 5

Deploy de Imagem Docker

project: meu-app
port: 8000
networks:
  - public

instances:
  canary:
    domain: canary.meusite.com.br
    source:
      type: image
      image: ghcr.io/usuario/meu-app:canary

Deploy Local (Hotfix)

project: meu-app
port: 8000
networks:
  - public

instances:
  hotfix:
    domain: hotfix.meusite.com.br
    source:
      type: local
      path: /builds/emergency/

CLI Tool / Scanner — Modo Exit

project: meu-scanner
build:
  dockerfile: Dockerfile

healthcheck:
  mode: exit        # Sem container permanente

github:
  repo: usuario/meu-scanner

version:
  source: git-commit

instances:
  production:
    source:
      type: dist

Com Staging de PRs

project: meu-app
port: 8000
networks:
  - public

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist
    keep_versions: 3

  staging:
    domain: staging.meusite.com.br
    source:
      type: branch
      branch: develop

  pr:
    domain_pattern: "pr-${PR_NUMBER}.staging.meusite.com.br"
    source:
      type: pr
    ephemeral: true
    ttl_hours: 48

staging:
  enabled: true
  max_instances: 5
  ttl_hours: 48

Deploy sem Traefik — Port-only (v2.0.1)

Para servidores backend sem Traefik local, acessiveis via proxy externo pela VPC:

project: meu-backend
build:
  dockerfile: Dockerfile
port: 80

ports:
  - "10.108.0.5:80:80"        # Bind na interface VPC

networks:
  - mysql

environment:
  DB_HOST: 10.108.0.3
  REDIS_HOST: 10.108.0.3

healthcheck:
  path: /health
  interval: 30s

instances:
  production:
    # SEM domain → runner pula Traefik, usa apenas ports
    source:
      type: branch
      branch: dev
    keep_versions: 3

Frontend Vite com `build.args` interpolados (v2.21.0+)

Frontends que precisam injetar variáveis em build time (Vite, Next.js, Angular). Os valores em build.args interpolam de .env/.secrets da app:

project: meu-frontend
build:
  context: .
  dockerfile: Dockerfile
  args:
    VITE_API_URL: "{{::API_URL}}"
    VITE_PUBLIC_DOMAIN: "{{::DOMAIN?app.example.com}}"
    BUILD_ENV: production

port: 80
networks:
  - public

healthcheck:
  mode: http
  path: /
  interval: 30s

instances:
  production:
    domain: app.example.com
    source:
      type: branch
      branch: main
    keep_versions: 3

Valores das vars (API_URL, DOMAIN) são gerenciados via runner env set <app> --key API_URL --value ... (ou wizard inicial). Resolução: .secrets primeiro, .env fallback, default opcional via ?default.

Multi-app com `depends_on:` cross-app (v2.21.0+)

App agent que precisa esperar LiveKit + Redis ficarem prontos antes de subir. Cada dep é polled via docker inspect até condition (healthy/started) ou timeout:

# meu-agent/.deploy.yml (depende de meu-livekit + meu-redis)

project: meu-agent
build:
  context: .
  dockerfile: Dockerfile

port: 8000
networks:
  - public

environment:
  LIVEKIT_URL: "ws://meu-livekit-prod:7880"
  REDIS_HOST: "meu-redis-prod"

instances:
  production:
    domain: agent.example.com
    source: { type: branch, branch: main }
    keep_versions: 3
    depends_on:
      - app: meu-livekit
        condition: healthy
        timeout: 300s
      - app: meu-redis
        condition: started
        timeout: 60s

Se meu-livekit ou meu-redis não atingir condition no timeout, o deploy falha com dependency_timeout: <app>/<instance> not ready after Ns. Bypass: --insecure.

Multi-Ambiente com Environment Overrides (v1.5.0)

project: meu-app
build:
  dockerfile: Dockerfile
port: 8000

environment:
  DB_HOST: mysql
  LOG_LEVEL: info

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist
    environment_overrides:
      DB_HOST: mysql-prod
      DB_NAME: app_prod

  staging:
    domain: staging.meusite.com.br
    source:
      type: branch
      branch: dev
    environment_overrides:
      DB_HOST: mysql-staging
      DB_NAME: app_staging

Exemplo Minimo

O menor .deploy.yml funcional:

# Modo IMAGE (imagem pronta)
project: meu-app
image: caddy:2-alpine
port: 80

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist
# Modo BUILD (com Dockerfile no repo)
project: meu-app
build:
  dockerfile: Dockerfile
port: 80

instances:
  production:
    domain: app.meusite.com.br
    source:
      type: dist

Validacao

Valide seu arquivo com:

runner validate --file /caminho/.deploy.yml

Gerando Template

Gere um template completo com:

runner generate-config > .deploy.yml
By Borlot.com.br on 28/02/2026