ckey simétrica e ckey assimétrica

O Runner suporta dois modos de encriptar os secrets de uma app. Esta página explica a diferença, quando escolher cada um, e como migrar.

Desde a v2.124.0 o padrão é a ckey assimétrica (--ckey sem valor = v2). A simétrica continua disponível (--ckey v1, ou um valor literal) — ela resolve um caso que a assimétrica não resolve (ver Quando ficar na simétrica). App existente não muda: o esquema é detectado pelo conteúdo do arquivo, e a conversão é sempre explícita (runner secrets migrate).

A diferença

ckey simétrica ckey assimétrica
Chave uma só, cifra e decifra par: privada no servidor, pública no repo
Bundle blob binário monolítico texto, valor a valor
Para escrever um secret precisa da chave que também lê tudo precisa só da pública
Para ler um secret qualquer um com a ckey lê todos precisa da privada, que não sai do servidor
Ver quais chaves existem não — o blob é opaco sim: nomes legíveis, valores selados
Fixar a chave manualmente sim não — a privada é do servidor

Como o bundle assimétrico se parece

# runner-secrets:v1
# instance:production
# recipient:age1jdfj2nx0lh2kc7us0syqrndn4w40pwquhr6tysrup4g8t988c3cqwuz27r
DATABASE_URL=enc1:hQEMAxk...
SMTP_PASSWORD=enc1:wV4DR2b...

Os nomes ficam visíveis; os valores são selados individualmente. É proposital: dá para revisar num diff de PR o que entrou ou saiu, sem que ninguém leia o conteúdo.

O cabeçalho diz runner-secrets:**v1** — isso é a versão do formato de arquivo, não do modo. Não confunda com o seletor --ckey v2 da linha de comando, que é o que escolhe o modo assimétrico.

O bundle simétrico, em contraste, é um blob binário: não dá para inspecionar nada sem a chave.

Quando escolher cada uma

Quando ficar na simétrica

  • Você precisa fixar uma chave específica para cifrar e decifrar — porque um processo externo precisa abrir o bundle, ou porque a mesma chave é compartilhada entre servidores ou ferramentas.
  • Você tem apps que compartilham a mesma ckey (ver runner ckeys copy).

Esse caso é o motivo de a simétrica continuar existindo mesmo com a assimétrica como padrão: no modo assimétrico a privada é do servidor por construção e não deve sair dele, então não há como "fixar uma chave" para uso externo.

Quando escolher a assimétrica

  • Você quer que escrever um secret não dê o poder de ler os outros — um operador ou automação adiciona valores tendo só a pública.
  • Você quer revisar em PR quais chaves entraram ou saíram, sem expor valores.
  • Você quer reduzir o raio de exposição: quem obtiver o repositório não decifra nada sem a privada do servidor.

Como usar

App nova, em modo assimétrico

runner add --repo owner/app --token ghp_xxx --ckey v2

O Runner gera o par, guarda a privada no state do servidor e grava a pública (recipient) no arquivo de secrets.

Migrar uma app da simétrica para a assimétrica

runner secrets migrate <app> --instance production

A migração é explícita e nunca acontece sozinha: faz backup, valida antes de publicar, é idempotente e preserva a ckey até a confirmação.

Secrets diferentes por instância: só na assimétrica

Na simétrica o .secrets em texto plano é um só por registro de app, para todas as instâncias. Production e staging no mesmo registro leem o mesmo arquivo, então uma chave com o mesmo nome tem o mesmo valor nas duas. O bundle cifrado já sai separado por instância (secrets.<instancia>.enc, ver abaixo), mas é cifrado a partir desse arquivo único.

Por isso o Runner recusa gravar um secret para uma instância que não é a ativa numa app simétrica (desde a v2.144.2):

secret_por_instancia_v1: 'app' usa o esquema v1 (ckey), em que o .secrets é um só por app —
gravar um secret para 'staging' sobrescreveria o da instância 'production' com a mesma chave. …

Antes, o valor caía no arquivo único e sobrescrevia o da outra instância sem aviso.

O caminho é migrar cada instância para a assimétrica, no servidor que a hospeda:

runner secrets migrate <app> --instance production   # no servidor de produção
runner secrets migrate <app> --instance staging      # no servidor de staging

Na assimétrica cada instância tem o seu arquivo (.runner/secrets.<instancia>.enc, com o seu recipient). A partir daí, runner env set <app> KEY VAL --secret -i staging grava só em staging.

Se a app não precisa de valores diferentes por instância — um registro por servidor, cada um com a sua instância, que é o caso comum — a simétrica continua servindo: cada servidor tem o próprio .secrets.

Como o Runner sabe qual modo usar

Duas fontes participam:

  1. O state do servidor — se a app tem uma identity registrada, é assimétrica.
  2. O cabeçalho do arquivo — # runner-secrets:v1 na primeira linha indica assimétrica.

No caminho normal as duas concordam e você não precisa pensar nisso.

Se elas divergirem — por exemplo, uma migração interrompida, ou um bundle restaurado de backup de outro modo — o deploy para com erro (scheme_mismatch), citando o que cada fonte diz, o caminho do arquivo inspecionado e como reconciliar. Isso é deliberado: subir um container sem os secrets corretos é pior do que não subir.

A partir da v2.69.0. Antes disso ninguém cruzava as duas fontes: com o state em assimétrica e o bundle em simétrica, o deploy subia verde e o container ficava sem nenhum secret — sem erro, sem aviso, sem log. Se você opera uma versão anterior, essa checagem não existe.

O que não muda entre os dois modos

  • A mkey (master key do servidor) continua igual, protegendo o state.
  • A precedência entre .env, .secrets e manifesto não muda — ver Hierarquia de env e secrets.
  • O required: do manifesto continua sendo o gate de obrigatoriedade.
  • O bundle continua no repositório da app, preservando a recuperação zero-touch.

Onde o bundle mora no repo (v2.129.0+)

Nos dois modos o arquivo cifrado vive em .runner/ do repo, um por instancia: .runner/secrets.<instancia>.enc (ex.: secrets.production.enc, secrets.staging.enc). Escrita e commit (fase 1.6, fase 8, runner secrets push) usam sempre esse nome.

O flat .runner/secrets.enc das versoes anteriores continua sendo lido enquanto for o unico arquivo (cadeia: bundle: explicito → per-target → per-instancia → flat), e no primeiro push e promovido (copia byte a byte) para o nome da instancia. Ninguem mais reescreve o flat — foi ele que fazia staging (branch dev) e producao (branch main) gravarem o mesmo binario e o merge de release conflitar (runner-client#150).

Ver também

By Borlot.com.br on 06/08/2026