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.

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