Introdução
Toda aplicação .NET precisa de configuração — connection strings, chaves de API, feature flags, URLs de serviços externos. O que muitos desenvolvedores não percebem é que o .NET 8+ possui um pipeline interno de configuração sofisticado, onde múltiplos provedores são carregados em uma ordem específica, e o último a registrar um valor vence. Entender essa ordem é a diferença entre uma aplicação que funciona “por sorte” e uma que funciona por design.
Neste artigo, vamos explorar como o WebApplication.CreateBuilder() monta o pipeline de configuração, a ordem exata dos Add... no ConfigurationBuilder, como secrets do Docker e Podman se integram ao .NET sem código adicional, e como IOptions<T>, IOptionsSnapshot<T> e IOptionsMonitor<T> reagem a mudanças de configuração em runtime. Se você já teve um bug causado por uma variável de ambiente que sobrescreveu seu appsettings.json — ou o contrário — este artigo vai esclarecer definitivamente o que acontece por baixo dos panos.
💡 Dica: Este artigo usa .NET 8+ como referência, mas o pipeline de configuração é fundamentalmente o mesmo desde o .NET 6. As novidades do .NET 8 são refinamentos, não mudanças estruturais.
O Que Acontece em WebApplication.CreateBuilder()
Quando você chama WebApplication.CreateBuilder(args), o .NET executa internamente uma sequência precisa de registros no ConfigurationBuilder. Essa é a ordem padrão dos providers de configuração:
| |
ℹ️ Informação: A regra é simples — o último provider a registrar um valor sobrescreve os anteriores. Por isso variáveis de ambiente vencem
appsettings.json, e argumentos de linha de comando vencem tudo.
A Ordem Completa e o Porquê
| Ordem | Provider | reloadOnChange | Quando é registrado |
|---|---|---|---|
| 1 | ChainedConfigurationSource | — | Configuração herdada do host (raro manipular) |
| 2 | appsettings.json | ✅ Sim | Sempre — valores padrão da aplicação |
| 3 | appsettings.{Environment}.json | ✅ Sim | Sempre — sobrescreve valores por ambiente |
| 4 | User Secrets (secrets.json) | ❌ Não | Apenas quando ASPNETCORE_ENVIRONMENT=Development |
| 5 | Environment Variables | ❌ Não | Sempre — variáveis do sistema/container |
| 6 | Command-line arguments | ❌ Não | Sempre — argumentos passados via CLI |
Essa hierarquia é intencional: valores mais seguros e específicos têm prioridade. O appsettings.json contém defaults; o ambiente (Development, Staging, Production) refina; secrets protegem dados sensíveis em dev; e variáveis de ambiente são o padrão em containers.
appsettings.json e Ambientes
O appsettings.json é o ponto de partida — ele contém os valores padrão da aplicação. O arquivo de ambiente (appsettings.{Environment}.json) não precisa conter todas as chaves — apenas as que deseja sobrescrever:
| |
| |
⚠️ Atenção: O
appsettings.Production.jsonnunca deve conter connection strings ou secrets de produção. Esses valores devem vir de variáveis de ambiente (containers) ou key vaults (cloud). O arquivo de produção serve apenas para ajustar comportamentos como log level, paginação e flags.
Como o .NET Determina o Ambiente
A variável ASPNETCORE_ENVIRONMENT (ou DOTNET_ENVIRONMENT para aplicações non-web) define qual appsettings.{Environment}.json será carregado. Os valores convencionais são:
| |
| |
dotnet user-secrets — Protegendo Dados em Desenvolvimento
O mecanismo de User Secrets resolve um problema real: desenvolvedores que commitam appsettings.json com connection strings de banco, chaves de API ou credenciais. Os secrets são armazenados fora do repositório, no perfil do usuário do sistema operacional.
Como Funciona
| |
Onde Ficam Armazenados
| Sistema Operacional | Caminho |
|---|---|
| Windows | %APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json |
| Linux/macOS | ~/.microsoft/usersecrets/<UserSecretsId>/secrets.json |
O UserSecretsId é um GUID gerado automaticamente no .csproj:
| |
💡 Dica: O
secrets.jsontem o mesmo formato doappsettings.json. Internamente, o .NET registra umJsonConfigurationProviderapontando para esse arquivo — por isso qualquer chave doappsettings.jsonpode ser sobrescrita via secret. Para entender como User Secrets se integram com EF Core Migrations em projetos multi-camada, veja EF Core Migrations: Multi-Projeto, Secrets e Scaffolding.
User Secrets NÃO Funcionam em Produção
O provider de User Secrets é registrado apenas quando o ambiente é Development:
| |
Em produção, os valores devem vir de variáveis de ambiente (containers), key vaults (Azure Key Vault, AWS Secrets Manager) ou Docker/Podman secrets.
Variáveis de Ambiente — O Padrão em Containers
As variáveis de ambiente são o provider mais importante em ambientes containerizados. Elas sobrescrevem tanto appsettings.json quanto appsettings.{Environment}.json.
Convenção de Nomenclatura
O .NET converte o separador hierárquico : para __ (duplo underscore) em variáveis de ambiente:
| |
| |
| |
⚠️ Atenção: No Linux, variáveis de ambiente são case-sensitive.
ConnectionStrings__Defaulté diferente deCONNECTIONSTRINGS__DEFAULT. O .NET trata ambas corretamente graças ao provider de Environment Variables, mas mantenha a convenção PascalCase com__para consistência.
Prefixo para Filtragem
Em ambientes compartilhados, use prefixo para evitar colisões:
| |
Docker e Podman Secrets — A Abordagem Segura para Containers
Variáveis de ambiente resolvem muitos cenários, mas têm uma limitação de segurança: elas ficam visíveis via docker inspect, podman inspect, /proc/*/environ e logs de debug. Para dados verdadeiramente sensíveis (senhas de banco, chaves de API, certificados), Docker e Podman oferecem secrets — arquivos montados em memória (tmpfs) dentro do container.
Como Secrets Funcionam no Docker e Podman
Os secrets são armazenados no host e montados como arquivos somente leitura no container, em /run/secrets/<nome>:
| |
| |
| |
Integrando Docker/Podman Secrets com .NET
O .NET não tem um provider nativo para /run/secrets/. A integração é feita via key-per-file provider — um provider de configuração que lê cada arquivo de um diretório como um par chave-valor, onde o nome do arquivo é a chave e o conteúdo é o valor:
| |
Com essa configuração, um arquivo /run/secrets/ConnectionStrings__Default com o conteúdo Host=db;Database=prod;Password=S3nh@! será mapeado para Configuration["ConnectionStrings:Default"].
ℹ️ Informação: O
AddKeyPerFileusa a convenção__→:automaticamente, igual às variáveis de ambiente. Então um arquivo chamadoApp__Email__SmtpHostserá acessível comoConfiguration["App:Email:SmtpHost"].
Convenção de Nomes para Secrets
Para manter a compatibilidade com a hierarquia do .NET:
| Secret (nome do arquivo) | Chave na Configuration | Conteúdo do arquivo |
|---|---|---|
ConnectionStrings__Default | ConnectionStrings:Default | Host=db;Database=prod;Password=S3nh@! |
App__Email__SmtpHost | App:Email:SmtpHost | smtp.sendgrid.net |
App__JwtSecretKey | App:JwtSecretKey | minha-chave-jwt-super-secreta-256bits |
Podman Secrets vs Docker Secrets
| Característica | Docker Secrets | Podman Secrets |
|---|---|---|
| Requisito | Docker Swarm mode | Funciona sem Swarm (rootless) |
| Armazenamento | Raft log (criptografado) | Diretório local (criptografável) |
| Montagem | /run/secrets/ (tmpfs) | /run/secrets/ (tmpfs) |
| Visível em inspect? | ❌ Não | ❌ Não |
| Visível em /proc? | ❌ Não | ❌ Não |
| CLI | docker secret create | podman secret create |
| Compose | docker compose com secrets: | podman-compose com secrets: |
💡 Dica: Se sua aplicação roda em Podman rootless (como descrito no deploy deste blog), prefira Podman secrets com
AddKeyPerFile. Eles não ficam expostos em logs, inspect ou variáveis de ambiente — uma camada de segurança extra sem complexidade adicional.
A Ordem Final Completa (com Docker Secrets)
Quando você adiciona todos os providers, incluindo Docker/Podman secrets, a ordem completa do pipeline fica:
| |
⚠️ Atenção: A posição em que você chama
AddKeyPerFiledefine sua prioridade. Se quiser que variáveis de ambiente tenham prioridade sobre Docker secrets, registre oAddKeyPerFileantes deAddEnvironmentVariables. A regra é sempre: o último registrado vence.
IOptions<T> e Suas Variantes
Com o pipeline montado, o próximo passo é consumir a configuração de forma tipada. O .NET oferece três interfaces no namespace Microsoft.Extensions.Options, e cada uma se comporta de forma diferente quanto ao ciclo de vida e ao recarregamento:
Registrando a Configuração Tipada
| |
| |
IOptions<T> — Singleton, Sem Reload
| |
| Característica | Comportamento |
|---|---|
| Ciclo de vida | Singleton (Value é calculado uma vez e cacheado para sempre) |
| Recarregamento | ❌ Não recarrega — se o appsettings.json mudar, Value continua com o valor original |
| Quando usar | Configurações que nunca mudam durante o ciclo de vida da aplicação |
| Registration | services.Configure<T>(section) |
IOptionsSnapshot<T> — Scoped, Reload por Request
| |
| Característica | Comportamento |
|---|---|
| Ciclo de vida | Scoped (novo valor a cada request HTTP / escopo de DI) |
| Recarregamento | ✅ Recarrega — se o appsettings.json mudar, o próximo request já vê o novo valor |
| Quando usar | APIs e web apps onde configurações podem mudar sem restart |
| Restrição | ⚠️ Não pode ser injetado em serviços Singleton |
| Registration | services.Configure<T>(section) (mesmo registro) |
IOptionsMonitor<T> — Singleton com Notificação
| |
| Característica | Comportamento |
|---|---|
| Ciclo de vida | Singleton (pode ser injetado em qualquer serviço) |
| Recarregamento | ✅ Recarrega — CurrentValue sempre retorna o valor mais recente |
| Notificação | ✅ OnChange() permite reagir a mudanças em tempo real |
| Quando usar | Serviços Singleton que precisam reagir a mudanças de configuração |
| Registration | services.Configure<T>(section) (mesmo registro) |
Comparativo Rápido
| Interface | Ciclo de Vida | Reload | Notificação | Usar em Singleton? |
|---|---|---|---|---|
IOptions<T> | Singleton | ❌ | ❌ | ✅ |
IOptionsSnapshot<T> | Scoped | ✅ (por request) | ❌ | ❌ |
IOptionsMonitor<T> | Singleton | ✅ (em tempo real) | ✅ OnChange() | ✅ |
Named Options — Múltiplas Instâncias da Mesma Configuração
Quando você tem múltiplas configurações do mesmo tipo (ex: vários provedores de email, múltiplos bancos de dados):
| |
| |
Validação de Configuração na Inicialização
Um erro comum é a aplicação iniciar com configuração inválida (connection string vazia, porta zerada) e só falhar quando o primeiro request chega. O .NET 8 permite validar na inicialização:
| |
| |
ℹ️ Informação: O
ValidateOnStart()faz a aplicação falhar rapidamente se a configuração estiver inválida — em vez de falhar misteriosamente no primeiro request. Isso é especialmente importante em containers onde um startup rápido com erro claro é melhor que um container “rodando” que falha em cada request.
Quando o .NET Recarrega Configuração Automaticamente
Nem todos os providers suportam reload automático. Saber quais recarregam (e quais não) evita bugs sutis:
Providers com reloadOnChange: true
Os providers baseados em arquivo são os únicos que suportam recarregamento automático:
| |
Quando você edita o appsettings.json ou appsettings.Production.json enquanto a aplicação está rodando, o .NET:
- Detecta a mudança via
FileSystemWatcher - Recarrega o provider de configuração afetado
- Emite um token de mudança (
IChangeToken) IOptionsSnapshot<T>passará a retornar o novo valor no próximo requestIOptionsMonitor<T>dispara o callbackOnChange()imediatamenteIOptions<T>não é afetado — mantém o valor original
Providers que NÃO Recarregam
| Provider | Recarrega? | Motivo |
|---|---|---|
appsettings.json | ✅ | reloadOnChange: true (padrão) |
appsettings.{Env}.json | ✅ | reloadOnChange: true (padrão) |
User Secrets (secrets.json) | ❌ | Registrado sem reloadOnChange |
| Environment Variables | ❌ | Variáveis são lidas na inicialização e cacheadas |
| Command-line args | ❌ | Passados uma vez na inicialização |
AddKeyPerFile (Docker secrets) | ❌ | Lidos na inicialização (arquivo em tmpfs) |
Forçando Reload com reloadOnChange
Se você precisa que outros arquivos JSON também recarreguem:
| |
⚠️ Atenção: Em containers Docker/Podman, o
reloadOnChangecomFileSystemWatcherpode não funcionar dependendo do tipo de volume montado. Volumes do tipobind mountgeralmente funcionam, mas volumes nomeados (docker volume) outmpfspodem não emitir os eventos de filesystem necessários. Teste sempre no seu cenário específico.
Customizando o Pipeline de Configuração
Quando os providers padrão não atendem, você pode customizar o pipeline completamente:
Adicionando Providers Customizados
| |
Substituindo Completamente o Pipeline
Em cenários raros onde você precisa controlar 100% da ordem:
| |
Dicas e Boas Práticas
Nunca commite secrets em
appsettings.json— usedotnet user-secretsem desenvolvimento e variáveis de ambiente ou Docker/Podman secrets em produção. Se um secret vazar no Git, considere-o comprometido imediatamente.Use
ValidateOnStart()para toda configuração crítica — connection strings, chaves de API, URLs de serviços. Falhar na inicialização é sempre melhor que falhar no primeiro request às 3h da manhã.Prefira
IOptionsMonitor<T>em serviços Singleton — se o serviço precisa reagir a mudanças de configuração. UseIOptionsSnapshot<T>apenas em serviços Scoped (controllers, services por request).Nomeie seus secrets de container com
__— useConnectionStrings__Defaultcomo nome do arquivo de secret para manter compatibilidade com a hierarquia de configuração do .NET.Cuidado com
reloadOnChangeem containers —FileSystemWatcherpode não funcionar com todos os tipos de volume. Teste o comportamento no seu ambiente antes de depender do reload automático.Use prefixo em variáveis de ambiente compartilhadas —
builder.Configuration.AddEnvironmentVariables(prefix: "MEUAPP_")evita colisões com variáveis do sistema e de outros serviços no mesmo host.Não misture configuração e código — a classe POCO de configuração deve ser simples (getters/setters). Lógica de negócio baseada em configuração deve ficar no serviço que consome o
IOptions<T>, nunca na classe de configuração.Documente a ordem do seu pipeline — em projetos com muitos providers (Key Vault, Feature Flags, Docker secrets), documente explicitamente a ordem de prioridade para a equipe.
Conclusão
O pipeline de configuração do .NET 8+ é um sistema poderoso e extensível que segue uma regra simples: o último provider registrado vence. Entender essa ordem — de appsettings.json (menor prioridade) até Docker/Podman secrets ou line arguments (maior prioridade) — é fundamental para evitar bugs sutis de configuração que só aparecem em produção.
O trio IOptions<T>, IOptionsSnapshot<T> e IOptionsMonitor<T> cobre todos os cenários de consumo: configuração estática (Singleton), configuração por request (Scoped) e configuração reativa com notificação (Singleton com reload). A combinação com ValidateOnStart() garante que sua aplicação falhe rápido com uma mensagem clara, em vez de falhar misteriosamente em runtime.
Para containers Docker e Podman, o AddKeyPerFile("/run/secrets") é a ponte entre os secrets do container engine e o sistema de configuração do .NET — seguro, simples e sem dependência de pacotes externos. Isso se combina perfeitamente com a arquitetura de deploy com Podman rootless e secrets gerenciados via pipeline.
O próximo passo natural é garantir que suas entidades de domínio e acesso a dados estejam igualmente desacoplados — e para isso, o EF Core 8 com Fluent API é o complemento ideal deste artigo.
Leia Também
- EF Core 8 com Fluent API: Mapeamento, ORM e Desacoplamento Total — desacoplamento no acesso a dados com mesmo padrão de configuração externalizada.
- EF Core Migrations: Multi-Projeto, Secrets e Scaffolding — como
dotnet user-secretsse integra com EF Core Migrations em projetos multi-camada. - Autenticação e Autorização: JWT, OAuth2 e OpenID Connect — configuração de segurança que depende diretamente do pipeline de configuração.
- BFF — Backend For Frontend: Segurança e Arquitetura — como tokens e secrets são gerenciados no padrão BFF.
- Design de APIs REST: Verbos HTTP e Parameter Binding — arquitetura de API que consome as configurações via DI.
Referências
- Configuration in ASP.NET Core — Microsoft Learn — documentação oficial do sistema de configuração do .NET, incluindo todos os providers e a ordem de carregamento.
- Options pattern in ASP.NET Core — Microsoft Learn — referência completa de
IOptions<T>,IOptionsSnapshot<T>,IOptionsMonitor<T>e Named Options. - Safe storage of app secrets in development — Microsoft Learn — guia oficial de
dotnet user-secretspara proteção de dados sensíveis em desenvolvimento. - Use multiple environments in ASP.NET Core — Microsoft Learn — como
ASPNETCORE_ENVIRONMENTcontrola o carregamento de configurações por ambiente. - Key-per-file configuration provider — Microsoft Learn — documentação do
AddKeyPerFilepara integração com Docker/Podman secrets. - Docker Secrets — Docker Docs — referência oficial de secrets no Docker Swarm e Docker Compose.
- Podman Secrets — Podman Docs — referência oficial do gerenciamento de secrets no Podman (rootless).
- .NET Generic Host — Microsoft Learn — como o host genérico do .NET configura o pipeline de configuração, logging e DI.
- WebApplicationBuilder Source Code — GitHub — código-fonte do
WebApplicationBuildermostrando a ordem exata dos providers registrados.
Ao comentar, você concorda com nossa Política de Privacidade, Termos de Uso e Política de Exclusão de Dados.