Dois deploys em producao da mesma branch
Voce tem um repositorio (ex.: um miniSMTP) e precisa rodar dois deploys em producao com configuracoes diferentes — dominios, HELO, credenciais de relay. Mesmo codigo, mesma branch, configs distintas.
Feito do jeito errado, um deploy sobrescreve o outro. Este guia mostra o jeito que nao quebra.
O que quebra (e por que)
Se voce simplesmente registra a mesma branch duas vezes:
- o
.deploy.ymle um so (esta na branch compartilhada) — colocar os dois configs literais nele faz um sobrescrever o outro; - o bundle cifrado tem um caminho no repo — dois registros escrevendo no mesmo nome se clobberam;
runner addbloqueia o segundo registro (exige--force-duplicate);- e ha uma nuance de qual instancia o deploy escolhe — resolvida na v2.55.1 (veja Qual instancia o deploy escolhe).
O anti-pattern e por o valor especifico de cada deploy dentro do manifesto. A branch e compartilhada; o manifesto so pode carregar o formato.
O principio
.deploy.yml → SHAPE: nome da instancia, {{::REFERENCIAS}}, literais COMUNS
secret.enc → VALORES de cada deploy (namespaced por instancia)Detalhes em Hierarquia env/secrets.
Saida recomendada: um manifesto por deploy
Cada deploy ganha seu proprio manifesto dentro do mesmo repo. Assim cada registro tem uma unica instancia — sem ambiguidade de "qual instancia deployar" — alem de app dir, ckey e bundle proprios.
1. Crie um manifesto por deploy no repo
seu-repo/
├── src/
├── deploy/
│ ├── cliente-a/.deploy.yml
│ └── cliente-b/.deploy.ymldeploy/cliente-a/.deploy.yml:
project: smtp-cliente-a
system: sys
type: sys
image: python:3.12-slim
port: 2525
github:
repo: usuario/smtp-mini
dist_branch: main
version:
source: git-commit
environment:
SMTP_MAX_SIZE: "26214400" # literal COMUM aos dois
SMTP_HELO: mx.clientea.com # literal DESTE deploy
secrets:
SMTP_RELAY_PASSWORD: "{{::SMTP_RELAY_PASSWORD}}" # referencia; valor no bundle
healthcheck:
tcp: 2525
instances:
cliente-a: # UMA instancia por manifesto
domain: mx.clientea.com
source:
type: dist
keep_versions: 3deploy/cliente-b/.deploy.yml e igual, trocando project, SMTP_HELO, o nome da instancia (cliente-b) e o dominio.
Como cada manifesto tem uma unica instancia, ela e o default — e tanto
runner deployquantofetch --deployacertam sozinhos.
2. Registre os dois (cada um com sua ckey)
runner add --repo usuario/smtp-mini --branch main \
--token "$(gh auth token)" \
--manifest-path deploy/cliente-a/.deploy.yml \
--slug smtp-cliente-a \
--instance cliente-a \
--ckey "<ckey-do-cliente-a>"
runner add --repo usuario/smtp-mini --branch main \
--token "$(gh auth token)" \
--manifest-path deploy/cliente-b/.deploy.yml \
--slug smtp-cliente-b \
--instance cliente-b \
--ckey "<ckey-do-cliente-b>"| Flag | Papel |
|---|---|
--manifest-path |
aponta o manifesto daquele deploy (implica --force-duplicate quando ja existe outro app do mesmo repo) |
--slug |
nome/diretorio proprio do registro (/data/apps/smtp-cliente-a) — isola .env/.secrets/state |
--instance |
fixa a instancia do registro e namespaceia o bundle (secrets.cliente-a.enc) |
--ckey |
chave propria — o bundle de A nunca abre com a ckey de B |
3. Preencha os valores sensiveis (por registro)
runner env set --secret smtp-cliente-a SMTP_RELAY_PASSWORD 'senha-do-A'
runner env set --secret smtp-cliente-b SMTP_RELAY_PASSWORD 'senha-do-B'Valor com caractere especial vai sempre aqui (o store e verbatim), nunca no manifesto — as aspas sao do seu shell.
4. Deploy
runner deploy smtp-cliente-a
runner deploy smtp-cliente-bCada um sobe com seu dominio, seu env e seus secrets. No repo, os bundles ficam separados:
.runner/
├── secrets.cliente-a.enc # so abre com a ckey de A
└── secrets.cliente-b.enc # so abre com a ckey de BAlternativa: manifesto unico com `instances:`
Se a config que diverge e apenas nao-sensivel (dominio, HELO, portas) e os secrets sao os mesmos, da pra usar um manifesto so com duas instancias:
instances:
cliente-a:
domain: mx.clientea.com
source: { type: dist }
environment_overrides: # prioridade maxima
SMTP_HELO: mx.clientea.com
cliente-b:
domain: mx.clienteb.com
source: { type: dist }
environment_overrides:
SMTP_HELO: mx.clienteb.comLimites desta alternativa:
- Uma ckey por registro.
state.ckeye um campo unico por app registrado — duas instancias no mesmo registro compartilham ckey e.env/.secrets. Se os secrets divergem, use a saida recomendada. - Rodar
runner set-ckeycom uma ckey diferente da ja registrada retorna erro loud (exit 1) sem escrever nada.
Qual instancia o deploy escolhe (v2.55.1+)
A partir da v2.55.1, runner deploy, runner deploy --all e runner fetch --deploy resolvem a instancia da mesma forma: state.instance (a instancia com que o app foi registrado via runner add --instance <N>) → default do manifesto → production.
Ou seja, num manifesto com duas instancias, cada registro deploya a sua instancia registrada — tanto no manual quanto no cron.
Desde a v2.151.2, a mesma ordem vale para os outros comandos de app que aceitam -i: rollback, health, restart, versions, container logs, config get/diff e env validate. Veja Um registro por instância no mesmo host.
runner deploy smtp-cliente-b # honra state.instance = cliente-b
runner fetch --deploy smtp-cliente-b # idemEm runners anteriores a v2.55.1, os deploys manuais (
deploy/--all) ignoravamstate.instancee miravam sempre a instancia default do manifesto — so ofetch --deployrespeitava o registro. Se voce esta numa versao antiga, atualize (runner self-update) ou registre um manifesto por deploy (a saida recomendada, que elimina a ambiguidade em qualquer versao).
Um registro por instância no mesmo host (v2.151.5+)
É a topologia de quem tem um manifesto com várias instâncias e põe dois ou mais registros do mesmo repo no mesmo servidor, um por instância. Exemplo: staging e staging-b de uma mesma API, cada um com sua branch e seu cofre.
runner add --repo usuario/minha-api --branch dev-a \
--instance staging --slug minha-api --ckey v2
runner add --repo usuario/minha-api --branch dev-b \
--instance staging-b --slug minha-api-b \
--force-duplicate --ckey v2O project: do manifesto é o mesmo nos dois registros. O que identifica cada um é o slug, que vira o nome do diretório (/data/apps/<slug>) e o label ccs.systems/uid dos containers dele.
Use runner v2.151.5 ou mais novo. A partir dessa versão:
| Comando | Comportamento com dois registros do mesmo repo |
|---|---|
instances, list, status, snapshot, health, versions |
cada registro mostra só os próprios containers (casados pelo slug, não pelo project:) |
unregister, destroy |
só tocam nos containers do registro pedido |
health, restart, versions, container logs, config get/diff, env validate, rollback |
sem -i, agem na instância do registro, e não em production |
restart, container logs |
acham o container pelos labels, inclusive com instância com hífen (staging-b) |
pause |
vale até o unpause: nem um fetch manual nem o cron desarmam a pausa |
edit -b |
valida a branch nova com a credencial do registro |
env validate |
faz a mesma conta do deploy na instância do registro |
Sintomas em versões anteriores. Se o servidor roda uma versão mais antiga, estes sintomas são esperados. A correção é runner self-update.
| Sintoma | Versão que corrige |
|---|---|
instances/list mostram [OK] com o container do outro registro e [--] no próprio, ou [OK] ao lado de not deployed |
2.151.1 |
unregister de um registro pode parar o container do outro |
2.151.0 |
runner fetch <app> manual numa app pausada e o cron volta a deployar |
2.151.0 |
edit -b <branch> responde "does not exist on remote" para uma branch que aparece listada logo abaixo |
2.151.0 |
env validate acusa como ausentes chaves que estão no cofre da instância, e não aceita -i |
2.151.0 |
rollback falha com Instance 'production' not found ou reverte a instância errada |
2.151.0 |
health/versions/restart sem -i consultam production |
2.151.2 |
restart responde ✓ Restarted sem reiniciar nada em instância com hífen |
2.151.3 |
depois de um rollback, status/list mostram [WARN] e a versão abandonada |
2.151.5 |
Verificação depois de atualizar:
runner -V # 2.151.5 ou mais novo
runner instances minha-api # [OK] só em staging
runner instances minha-api-b # [OK] só em staging-b
docker ps --format '{{.Names}} {{.Label "ccs.systems/uid"}}' | grep minha-api
runner env validate minha-api-b # valida a instância do registroCada registro deve mostrar [OK] só na própria instância, e o uid de cada container deve ser o slug do registro dele.
Antes de agir, confira a identidade (v2.152.0+). runner preflight <slug> mostra a instância e de onde ela veio, o cofre, o container e o router que o próximo deploy vai usar. Ver runner preflight.
Fixe a instância de cada registro. Registro sem --instance, num manifesto com várias instâncias, resolve pela ordem do bloco instances:. Desde a v2.152.0, list, status, preflight e doctor avisam nesse caso. Corrija com runner edit <slug> -i <instância>; numa app v2, o cofre da instância nova é criado na hora (v2.152.1+).
Copiar um secret entre os registros sem passá-lo pela tela:
runner secrets copy --from minha-api --to minha-api-b --keys APP_KEYVerificacao
runner list # os dois registros, dominios distintos
runner manifest smtp-cliente-a # manifesto resolvido daquele deploy
runner secrets show smtp-cliente-a # abre so com a ckey de A
runner status smtp-cliente-bArmadilhas
| Nao faca | Por que |
|---|---|
| Dois blocos de config literais no mesmo manifesto | a branch e compartilhada — um sobrescreve o outro |
| Mesma ckey nos dois deploys | vazou uma, abriu as duas (perde o blast-radius) |
Mesmo --slug (ou sem slug) |
os registros colidem no mesmo app dir |
Sem --instance |
os bundles colidem no mesmo nome no repo |
| Secret com caractere especial no manifesto | o YAML reinterpreta $, {}, # — use env set --secret |
Veja tambem
- Hierarquia env/secrets — o contrato
secret.enc > env > deploy.yml. - Secrets por instancia — isolamento por servidor e o failover de resolucao do bundle.
- Anatomia do .deploy.yml — schema completo.