Classificação automática de env/secrets

A partir da v2.33.0, ao gerar o .deploy.yml o Runner classifica cada variável sozinho. Você não responde mais variável por variável. O motor lê .env/.env.example, o ENV do Dockerfile e o environment: do compose, decide o que é environment: (plain) e o que é secrets:, e escolhe como cada secret é resolvido.

O mesmo motor roda no runner wizard (CLI) e no runner add (quando o repo não tem .deploy.yml). É determinístico — sem IA, sem chute. As mesmas entradas geram sempre o mesmo manifesto.

Onde roda

  • runner wizard — gerador interativo, com tela de revisão.
  • runner add — geração automática quando o repo não tem .deploy.yml.
  • runner wizard --accept-defaults e ambientes sem terminal (CI, SSH) — modo não-interativo.

Regra 1 — env vs secret

O motor decide entre environment: e secrets: por estas regras, na ordem. A primeira que casar manda:

Ordem Regra Vai para Exemplos
1 Prefixo público environment: NEXT_PUBLIC_*, VITE_*, REACT_APP_*, NUXT_PUBLIC_*, EXPO_PUBLIC_*, PUBLIC_*
2 Sufixo de infra environment: *_URL, *_HOST, *_PORT, *_PATH, *_DIR, *_ENDPOINT
3 Nome de secret secrets: *_SECRET, *_PASSWORD, *_TOKEN, *_API_KEY, etc.
4 Nenhuma environment: qualquer var sem sinal de secret

O prefixo público vence mesmo com KEY/TOKEN no nome. NEXT_PUBLIC_SUPABASE_ANON_KEY vai para environment: — é pública por design (o build embute ela no bundle do navegador). Já DATABASE_URL, apesar do sufixo _URL, é tratada como secret (a regra 3 de secret HIGH tem prioridade sobre o sufixo de infra).

Regra 2 — como resolver cada secret

Para o que é secret, o motor decide o modo de resolução por categoria:

Categoria Exemplos Resolução no .deploy.yml
Auto-gerável JWT_SECRET, SESSION_TOKEN, CSRF_TOKEN, APP_KEY, SECRET_KEY "${GENERATE:hex:64}" — o Runner gera um valor novo
Externo STRIPE_SECRET_KEY, SMTP_PASSWORD, GITHUB_TOKEN, SENDGRID_KEY "{{::KEY}}" + entra em required: — você fornece no deploy
Ambíguo DATABASE_URL, DB_PASSWORD, secrets genéricos Pergunta (interativo) ou "{{::KEY}}" + required: (não-interativo)

Duas decisões importantes:

  • Gera novo para produção. Se um secret auto-gerável já tem valor no .env (provavelmente um valor de desenvolvimento), o Runner ignora esse valor e gera um novo. Produção não herda o segredo de dev.
  • Banco é sempre ambíguo. Senhas e URLs de banco (DATABASE_URL, DB_PASSWORD, REDIS_URL...) nunca são decididas sozinhas — podem ser de um banco interno (gerável) ou externo (você fornece). O Runner pergunta, ou no não-interativo trata como externo ({{::}} + required:).

Pergunta só o ambíguo (modo interativo)

No runner wizard interativo, o Runner resolve sozinho tudo que é claro. Ele só pergunta as variáveis ambíguas:

? ambíguo: DATABASE_URL
  classificar como:
  > secret: pedir no deploy ({{::}})
    secret: usar valor do .env
    secret: gerar novo
    plain (manter valor)

Tela de revisão

Depois de resolver, o wizard mostra uma tabela consolidada para você conferir antes de gravar. Valores de secret aparecem mascarados:

━━━ Revisão env/secrets ━━━
  NEXT_PUBLIC_API_URL    plain  https://api.x  # prefixo público (client-side) → plain
  LOG_LEVEL              plain  info           # sem sinal de secret → plain
  JWT_SECRET             secret [gerar novo]   # self-mintable → gerado novo p/ prod
  STRIPE_SECRET_KEY      secret ••••••         # provedor externo → required
  DATABASE_URL           secret {{::deploy}}   # ambíguo (você escolheu prompt)

Comentários `# DETECTED:` no manifesto

O .deploy.yml gerado documenta de onde veio cada decisão. Isso torna o resultado auditável — você entende e edita com segurança:

environment:
  NEXT_PUBLIC_API_URL: https://api.x  # DETECTED: prefixo público (client-side) → plain
  LOG_LEVEL: info                     # DETECTED: sem sinal de secret → plain
  BASE_DOMAIN: app.exemplo.com        # DETECTED: derivado: domínio da instância

secrets:
  JWT_SECRET: "${GENERATE:hex:64}"               # DETECTED: self-mintable → gerado
  STRIPE_SECRET_KEY: "{{::STRIPE_SECRET_KEY}}"   # DETECTED: provedor externo → required
  DATABASE_URL: "{{::DATABASE_URL}}"             # DETECTED: banco → ambíguo

required:
  - STRIPE_SECRET_KEY
  - DATABASE_URL

`required:` + fail-fast

Secrets externos sem valor entram no bloco required:. No deploy, o Runner aborta cedo — antes de tocar o Docker — se alguma variável de required: faltar ou estiver vazia:

runner deploy minha-app
# Error: missing_required: STRIPE_SECRET_KEY, DATABASE_URL

Forneça os valores e o deploy passa:

runner env set minha-app --key STRIPE_SECRET_KEY --value sk_live_... --secret
runner env set minha-app --key DATABASE_URL --value postgres://... --secret
runner deploy minha-app

Env derivável

O Runner injeta variáveis que dá para deduzir do contexto, sem você definir:

Variável De onde vem
BASE_DOMAIN domínio da instância de produção
CORS_ORIGINS vazio, auto-derivado de BASE_DOMAIN (apenas system: sys/api)

Modo não-interativo

Sem terminal para perguntar (runner add, --accept-defaults, CI, SSH), o motor aplica os defaults seguros:

  • Auto-gerável → ${GENERATE:hex:64}.
  • Externo e ambíguo → {{::KEY}} + required: (você fornece no deploy).

Nunca grava placeholder vazio (KEY: "") e nunca vaza valor real no manifesto.

Prompt de secret é mascarado (v2.54.0+)

Quando um secret resolvido como "{{::...}}" é pedido no primeiro deploy (via runner add --deploy ou runner deploy), o input entra mascarado — o valor digitado aparece como *** e nunca é ecoado no terminal ou no scrollback:

  Praxis merchant secret (oculto): ***********************

Só os campos de secrets: são ocultos. Os de environment: (plain) continuam em texto claro, já que não são sensíveis. Isso evita que um secret externo (chave de gateway, senha de banco) fique registrado no histórico do terminal ou em logs de sessão.

Editar depois

A classificação é um ponto de partida. Você sempre pode editar o .deploy.yml gerado. A sintaxe dos placeholders continua válida e documentada em Secrets lifecycle:

  • "{RANDOM}" / "${GENERATE:hex:N}" — gera valor.
  • "{{::Label?default}}" — pede no deploy (com default opcional).
  • valor literal — usado direto.

Veja também

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