`runner secrets`
Conjunto de subcommands para gerenciar secrets criptografados de uma app.
Secrets são armazenados em duas camadas:
.env/.secretsplaintext em/data/apps/<app>/— lidos pelo bind mount do container.runner/secrets.enc(AES-256-GCM) — backup criptografado para recovery zero-touch em outro servidor
A partir de v2.23.3, todos os comandos de escrita atualizam as 3 fontes atomicamente.
Subcomandos
| Comando | Função | Desde |
|---|---|---|
runner secrets show <app> |
Exibe valores decifrados | v2.10.0 |
runner secrets set <app> <key> <value> |
Salva valor plaintext em .secrets |
v2.23.0 |
runner secrets generate <app> <key> --generator <spec> |
Gera valor via pattern e salva | v2.23.0 |
runner secrets share <app> <key> --reference <ref> |
Referência cross-project (sys orchestrator) | v2.23.0 |
runner secrets delete <app> <key> |
Remove uma key | v2.23.4 |
runner secrets paste-encoded |
Import cross-server de valor encriptado | v2.10.0 |
runner secrets push <app> |
Push de .deploy.yml + .runner/secrets.enc pro repo |
v2.23.1 |
runner secrets copy |
Copia keys entre projetos no mesmo servidor | v2.10.0 |
runner secrets export |
Exporta keys como blob sealed | v2.10.0 |
runner secrets import-sealed |
Importa blob sealed | v2.10.0 |
`runner secrets show `
Decifra .runner/secrets.enc e exibe os valores.
runner secrets show meu-app
runner secrets show meu-app --jsonOutput indica explicitamente o source (a partir de v2.23.0):
--- env (from .runner/secrets.enc) ---
APP_NAME=meu-app
PORT=3000
--- secrets (from .runner/secrets.enc) ---
DATABASE_URL=postgres://user:pass@db:5432/app
JWT_SECRET=AbC123...
`runner secrets set ` (v2.23.0+)
Salva um valor plaintext direto no .secrets cifrado. Substitui o antigo paste-encoded --mode manual.
runner secrets set meu-app DB_PASSWORD "s3cr3t"
runner secrets set meu-app SMTP_USER admin@example.com --jsonA partir de v2.23.3, escreve atomicamente em .env/.secrets/.runner/secrets.enc. Próximo deploy enxerga o valor sem precisar refazer encoding.
`runner secrets generate --generator ` (v2.23.0+)
Gera um valor aleatório via spec e salva. Substitui paste-encoded --mode generate.
runner secrets generate meu-app JWT_SECRET --generator '${GENERATE:hex:64}'
runner secrets generate meu-app API_TOKEN --generator '{RANDOM}'Specs suportadas:
| Spec | Resultado |
|---|---|
{RANDOM} |
32 chars alfanuméricos |
${GENERATE:hex:N} |
N chars hexadecimais |
${GENERATE:base64:N} |
N bytes em base64 |
`runner secrets share --reference ` (v2.23.0+)
Declara uma referência cross-project (ex: @ccs/SMTP_PASS). A resolução ocorre via sys orchestrator — o CLI sozinho não consegue resolver referências cross-project.
runner secrets share meu-app SMTP_PASS --reference @ccs/SMTP_PASSSubstitui paste-encoded --mode shared.
`runner secrets delete ` (v2.23.4+)
Remove uma key do app. Procura em .env e .secrets (nessa ordem), remove de onde encontrar, reescreve os 3 stores atomicamente.
runner secrets delete meu-app TEST_FOO # prompt interativo
runner secrets delete meu-app TEST_FOO --yes # skip confirmation
runner secrets delete meu-app TEST_FOO --json # programático| Flag | Default | Descrição |
|---|---|---|
--force, -y, --yes |
false |
Skipa o prompt interativo |
--json |
false |
Output em JSON |
Key ausente é no-op silencioso (removed: false).
{
"action": "secrets_delete",
"app": "meu-app",
"key": "TEST_FOO",
"removed": true,
"removed_from": ".secrets",
"deleted_at": "2026-05-16T..."
}Comportamento ao deletar a última key (v2.23.5+)
Quando a operação resulta em ambos os maps (.env + .secrets) vazios, runner remove o .runner/secrets.enc em vez de manter um bundle encriptado vazio. .env e .secrets plaintext ficam como files de 0 bytes (docker bind mount lê como "sem env"). Próximo secrets set repopula normalmente.
Antes da v2.23.5, runner secrets delete da última key falhava com No .env or .secrets content to encrypt — bug corrigido.
`runner secrets paste-encoded` (legacy + cross-server)
Import de um valor já encriptado com a ckey de outro projeto/servidor.
runner secrets paste-encoded --project meu-app --key DB_PASSWORD \
--encoded-value 'enc:v1:...' \
--source-ckey ck_a1b2…c3d4A partir de v2.23.0, os modos --mode manual|generate|shared continuam funcionando mas emitem deprecation warning — prefira os subcommands explícitos (set/generate/share).
`runner secrets push ` (v2.23.1+)
Push de .deploy.yml + .runner/secrets.enc pro repo do app. Usa o token GitHub já encriptado no state da app — zero credentials por flag/env.
runner secrets push meu-app
runner secrets push meu-app --jsonUse case
Você acabou de registrar a app via runner add --ckey, o runner gerou .deploy.yml + .runner/secrets.enc localmente, mas o repo no GitHub ainda não tem esses files. Pra recovery zero-touch funcionar em outro server, eles precisam estar no repo. Antes de v2.23.1 isso só ocorria após o primeiro deploy bem-sucedido (Phase 8 do pipeline).
Garantias de segurança (encrypted-only por design)
- Whitelist de staging: só
git add .deploy.yml .runner/secrets.enc(paths explícitos). Nuncagit add ./git add -A. Plaintext.env/.secretsnão consegue ser staged. - Auto-fix do
.gitignore(v2.23.2+): se faltarem.env/.env.*/.secrets/.secrets.*no.gitignore, runner injeta no mesmo commit do bootstrap. - Reescrita de
.runner/(v2.23.2+): se o.gitignoretem.runner/(template comum), reescreve como.runner/*+ adiciona!.runner/secrets.enc. Sem essa reescrita, a negação não funciona em git e osecrets.encficaria invisível. - Encryption-required: se
.deploy.ymldeclarasecrets:mas não há.runner/secrets.enc, encripta on-the-fly. Sem ckey ou.env/.secretspra encriptar → erro claro, sem push.
Output JSON
{
"action": "secrets_push",
"app": "meu-app",
"repo": "owner/repo",
"branch": "main",
"pushed": true,
"commit": "abc123456789",
"no_changes": false,
"pushed_at": "2026-05-16T..."
}pushed: false + no_changes: true significa que o repo já tem o estado atual (idempotente).
Auto-bootstrap no `runner add`
A partir de v2.23.1, quando --ckey e token estão disponíveis E --deploy não foi passado, runner add tenta o push automaticamente ao final. Failure não falha o add — operador pode retry via runner secrets push <app>.
Erros comuns
| Erro | Causa | Fix |
|---|---|---|
push_no_token |
App não tem token no state | Re-registrar com --token-from <outra-app> |
push_no_ckey |
App não tem ckey | Re-registrar com --ckey "<valor>" |
push_no_encrypted_bundle |
.deploy.yml declara secrets: mas não há .env/.secrets nem secrets.enc |
runner secrets set <app> <K> <V> para cada key |
push_clone_failed |
Token sem permissão de push no repo | Regenerar PAT com escopo repo |
| Container sobe com secret vazio | Pre-v2.23.3: divergência entre stores | Atualizar pra v2.23.3+ |
Audit trail (v2.23.6+)
Cada show/set/generate/delete/paste-encoded registra uma linha JSON em /opt/runner/logs/secrets-access.jsonl (perm 0640 root:devs):
{
"timestamp": "2026-05-16T17:58:58Z",
"action": "show",
"app": "meu-app",
"keys": ["DB_PASSWORD", "JWT_SECRET"],
"user": "root",
"uid": 0,
"hostname": "ns3177045",
"pid": 1067444
}| Campo | Significado |
|---|---|
action |
show / set / generate / delete / paste-encoded:<mode> |
app |
Nome do app afetado |
keys |
Nomes das keys envolvidas (NUNCA valores) |
user / uid |
Output de whoami + getuid() do processo |
hostname / pid |
Útil pra correlacionar com syslog/auditd |
Escrita é best-effort — falha de I/O não aborta a operação. Trail é append-only.
Queries comuns
# Todos os acessos das últimas 24h
jq -c 'select(.timestamp > (now - 86400 | strftime("%Y-%m-%dT%H:%M:%SZ")))' \
/opt/runner/logs/secrets-access.jsonl
# Quem rodou `show` em qual app
jq -c 'select(.action == "show") | {timestamp, app, user, keys: .keys | length}' \
/opt/runner/logs/secrets-access.jsonl
# Detectar repetição suspeita (mesmo user batendo show muitas vezes)
jq -r 'select(.action == "show") | "\(.user) \(.app)"' \
/opt/runner/logs/secrets-access.jsonl | sort | uniq -c | sort -rn | headVeja também
- `runner ckeys` — gerenciamento da content key
- `runner tokens` — gerenciamento do token GitHub
- `runner env` — alternativa pra setar env/secrets via
env set --secret - Secrets e Encriptacao — modelo de duas camadas (mkey + ckey)