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-defaultse 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_URLForneç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-appEnv 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
- Secrets lifecycle — o que acontece depois:
.env/.secrets, bundle encriptado, injeção no container. - Anatomia do .deploy.yml — schema completo do manifesto.
- Wizard state machine — o fluxo de perguntas do
runner wizard.