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 (
--ckeysem 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 v2da 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 v2O 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 productionA 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 stagingNa 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:
- O state do servidor — se a app tem uma identity registrada, é assimétrica.
- O cabeçalho do arquivo —
# runner-secrets:v1na 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,.secretse 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
- Secrets lifecycle — o ciclo completo, das fases de deploy à injeção no container
- Hierarquia de env e secrets — quem vence quem
- Secrets por instância — bundles separados por instância