Secrets por Instancia (v2.44.0)

Como isolar completamente os secrets de cada instancia (producao, staging, homologacao) quando cada uma roda em um servidor diferente.

Conceito

O isolamento e por servidor. Cada servidor hospeda uma instancia daquele deploy e conhece apenas a ckey daquela instancia — a ckey de producao nunca existe no servidor de staging/dev.

Um mesmo servidor pode hospedar varios registros de app (inclusive o mesmo repo duplicado), mas cada registro corresponde a uma instancia e a uma ckey (state.ckey). Nao existe "uma ckey por servidor" — existe uma ckey por registro de app.

Servidor STAGING (dev)          Servidor PRODUCAO (ops)
├── Runner                      ├── Runner
├── app: meu-app                ├── app: meu-app
├── instance: staging           ├── instance: production
├── ckey: <staging-key>         ├── ckey: <production-key>
└── decifra so                  └── decifra so
    secrets.staging.enc             secrets.production.enc

Layout de Arquivos

Dentro de .runner/ no repositorio:

.runner/
├── secrets.enc                  # global/legado (fallback)
├── secrets.production.enc       # cifrado com a ckey de producao
└── secrets.staging.enc          # cifrado com a ckey de staging
  • secrets.enc continua existindo para apps antigos — comportamento byte-identico ao anterior a essa feature.
  • secrets.<instancia>.enc e o arquivo por-instancia, nomeado por convencao (production, staging, homolog, etc — o mesmo nome usado em --instance).

Failover de Resolucao no Deploy

No deploy, o runner decide qual arquivo decifrar seguindo esta ordem — o primeiro que existir vence:

1. config.bundle explicito no .deploy.yml da instancia
        ↓ (se ausente)
2. secrets.<instancia>.enc (por convencao de nome, sem precisar declarar nada)
        ↓ (se ausente)
3. secrets.enc global (legado)

Ausencia de qualquer um deles nunca e erro — o runner simplesmente segue para a proxima opcao da lista. Um app antigo que so tem secrets.enc continua funcionando exatamente como antes.

read = write = commit

O deploy le, re-cifra (fase 1.6 do pipeline) e commita o mesmo arquivo por-instancia que resolveu no failover acima. Ou seja, runner secrets set rodado num servidor persiste no secrets.<instancia>.enc daquele ambiente no proximo deploy — nao mistura com o arquivo de outra instancia.

Estruturando com `runner set-ckey`

O comando runner set-ckey cifra .env/.secrets locais em secrets.<instancia>.enc e registra a ckey no vault/state local do servidor.

runner set-ckey <app> --instance <NAME> [--ckey <VALOR>] [--json]

Sem --ckey, o comando le a ckey de stdin sem eco (mascarada) — nunca ecoa o valor e nunca grava a ckey no .deploy.yml.

Regra de ouro: rode no servidor dono daquela ckey

  • Ops roda no servidor de producao — a ckey de producao nunca sai de la.
  • Dev roda no servidor de staging — a ckey de staging fica isolada do lado de producao.
  • O dev nunca toca a ckey de producao.

Fluxo operacional

Servidor STAGING (dev):   runner set-ckey app --instance staging --ckey <staging-key>
                           → secrets.staging.enc; git commit + push

Servidor PROD (ops):      runner set-ckey app --instance production   # ckey via stdin
                           → secrets.production.enc; git commit + push

Deploy (cada servidor):   fetch → decifra so o SEU secrets.<instancia>.enc → container

O .enc e seguro no git (esta cifrado) — cada servidor decifra apenas o do ambiente que hospeda.

Ckey ja existente e diferente

Se o app ja tem uma ckey diferente registrada no state daquele servidor, runner set-ckey retorna erro loud (exit 1) sem escrever nada. O caso "mesmo servidor, ckeys diferentes para a mesma instancia" nao e suportado — se voce precisa disso, revise a topologia (provavelmente deveria ser dois registros de app distintos, ou dois servidores).

Provisionando via `runner add`

Quando a instancia nao e a primaria do registro (N != instancia primaria), runner add --instance <N> --ckey <K> ja provisiona o .enc correspondente no momento do registro:

runner add --repo usuario/meu-app --branch main --instance staging --ckey "<staging-key>"

Bloco `config:` Opcional

Por padrao, a convencao de nome (secrets.<instancia>.enc) e suficiente — nao precisa declarar nada no .deploy.yml. O bloco config: por instancia so e necessario quando voce quer um bundle nao-convencional ou (fora de escopo nesta versao) vault:

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

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

Fronteira Dev ↔ Prod: A Moldura Honesta

E importante entender exatamente o que essa separacao garante — e o que ela nao garante sozinha.

Blast-radius: sempre garantido

secrets.staging.enc vazado nunca abre producao, porque as ckeys sao distintas. Isso vale independente de onde os arquivos rodam — mesmo servidor ou servidores diferentes.

Acesso-isolado: so via separacao fisica de servidor

Isolamento de acesso (um dev nao conseguir ler os secrets de producao) so existe quando voce separa por servidor. Num host compartilhado, um dev com shell e a master key (mkey) daquele host consegue decifrar o record de producao se ele estiver la — o codigo suporta acesso-isolado quando voce separa por servidor, ele nao fabrica esse isolamento sozinho num host compartilhado.

Em outras palavras: separe producao e staging em servidores fisicos (ou VMs) diferentes se acesso-isolado for um requisito real, nao apenas blast-radius.

Veja Tambem

By Borlot.com.br on 05/07/2026