Hierarquia de fonte-da-verdade (env/secrets)

Um valor de ambiente pode existir em mais de um lugar: no .deploy.yml, no .env/.secrets local e no bundle encriptado .runner/secrets.enc. Esta pagina define quem vence quando o mesmo KEY aparece em mais de um, e como isso evita que um valor real seja sombreado por vazio.

O contrato

secret.enc  >  env/.secrets (local)  >  deploy.yml
(bundle)       (valor do operador)      (defaults do manifesto)
  • .runner/secrets.enc (bundle) — a fonte autoritativa e recuperavel. Um valor real no bundle nunca e sombreado por uma linha local vazia.
  • .env / .secrets (local) — o valor efetivo em runtime; e o que o env set grava. Um valor real aqui vence um bundle desatualizado (o operador acabou de setar).
  • .deploy.yml — o piso: so fornece o que ele mesmo possui (literais e geradores). Nunca escreve por cima das camadas de cima.

A partir da v2.55.0 esse contrato e garantido no codigo: {{::}} que resolve vazio nao vira linha KEY=, e o merge do bundle nao deixa vazio local sombrear valor real.

Os tres papeis do manifesto

Dentro de environment: / secrets: do .deploy.yml convivem tres papeis. Saber qual e qual evita o erro classico de "setei e nao pegou":

Sintaxe Papel O manifesto e dono? Efeito
KEY: "valor" valor literal (config, default) Sim escreve — piso (so se a chave falta)
KEY: "{RANDOM}" / "${GENERATE:hex:32}" gerador Sim (gera uma vez) escreve o valor gerado
KEY: "{{::KEY}}" referencia — dono e env/secret.enc Nao nunca escreve; interativo pergunta, nao-interativo e no-op

O ponto-chave: {{::KEY}} nao e um valor, e um ponteiro dizendo "esse valor vem de fora". Num deploy nao-interativo (CI, fetch --deploy) ele nao tem como perguntar, entao nao materializa nada — a chave fica ausente e o valor real (do .secrets/bundle) e quem manda.

Um literal vazio proposital (KEY: "", sem {{::) continua sendo escrito — vazio intencional e respeitado.

Por que "setei e nao pegou" acontecia (< v2.55.0)

Antes da v2.55.0, um {{::KEY}} num deploy nao-interativo resolvia para "" e materializava KEY= no .env/.secrets. Essa linha vazia era pegajosa: o merge nunca a sobrescrevia e a hidratacao do bundle preferia o local — entao o valor real (no secrets.enc) ficava permanentemente sombreado por vazio. Sintoma tipico: app sobe com DATABASE_URL="" e o Hibernate/driver falha na conexao mesmo com o valor "setado".

A correcao (v2.55.0) tem dois lados, espelhando o contrato:

  • deploy.yml nunca envenena: {{::}} que resolve vazio e descartado, nao vira linha vazia.
  • secret.enc vence o vazio: no merge do bundle, uma linha local vazia cede ao valor nao-vazio do bundle.

Onde por cada valor

Tipo de valor Onde declarar Por que
Config / default nao-sensivel environment: no .deploy.yml (literal) o manifesto e dono; e o piso
Segredo (senha, token, chave, URL com credencial) store, via runner env set --secret verbatim, encriptado no bundle, recuperavel
Valor que vem do operador/CCS por deploy {{::KEY}} no manifesto + valor no store manifesto so referencia
Gerado uma vez {RANDOM} / ${GENERATE:hex:N} o runner gera e persiste

Regra pratica: valor com caractere especial ou sensivel vai no store (env set --secret), nunca como literal no manifesto YAML.

Caracteres especiais

O store (.env/.secrets) e verbatim: o runner guarda e injeta o valor byte-a-byte. O manifesto YAML, ao contrario, reinterpreta varios tokens. Por isso valor com caractere especial pertence ao store.

Caractere no valor No store (env set) No manifesto (.deploy.yml)
$ (p@$$word) verbatim — docker --env-file nao expande ${...} e tratado como referencia compose (some)
{ } verbatim colide com {{::}} / {RANDOM} / ${GENERATE}
# literal (comentario so no inicio da linha) YAML: inicia comentario se nao citar
= preservado (divide so no 1º =) ok, se citar no YAML
espaco / : / * / ! / & verbatim (trailing e trimado) precisa de aspas YAML
aspas " ' literais (nao encapsulam) aspas YAML (escape)
newline (PEM, JSON multi-linha) nao suportado — use base64 ou mount de arquivo idem

Aspas sao do shell

Ao usar env set, as aspas sao do seu shell, nao do valor. O shell consome as aspas e entrega o literal ao runner:

# certo: o shell tira as aspas, o runner guarda: p@$$w{}rd#1
runner env set --secret meu-app DB_PASSWORD 'p@$$w{}rd#1'

# errado: aspas dentro do valor viram literais
runner env set --secret meu-app DB_PASSWORD '"senha"'   # guarda: "senha" com aspas

Multi-linha (PEM, JSON)

O formato --env-file do docker e por-linha; um valor nao pode conter \n. Para segredos multi-linha:

  • base64 — encode no env set, a app decodifica em runtime; ou
  • mount de arquivo — o segredo vira arquivo no container.

Veja tambem

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