---
title: "Configuração em Go: Variáveis de Ambiente, Viper e Twelve-Factor"
url: "https://golang.com.br/blog/configuracao-go-viper-variaveis-ambiente/"
markdown_url: "https://golang.com.br/blog/configuracao-go-viper-variaveis-ambiente.MD"
description: "Configuração em Go na prática: variáveis de ambiente, envconfig, Viper com YAML, defaults, validação fail-fast, segredos e quando usar cada abordagem. Com exemplos de produção."
date: "2026-07-17"
author: "Golang Brasil"
---

# Configuração em Go: Variáveis de Ambiente, Viper e Twelve-Factor

Configuração em Go na prática: variáveis de ambiente, envconfig, Viper com YAML, defaults, validação fail-fast, segredos e quando usar cada abordagem. Com exemplos de produção.


Todo serviço Go de verdade precisa de configuração: porta do servidor, URL do banco, timeout de requisição, nível de log, segredos de autenticação. A pergunta não é *se* você vai configurar a aplicação, mas *onde* essa configuração mora. Hardcoded no código fonte é o pior lugar; variáveis de ambiente e arquivos estruturados são o caminho profissional. Este guia mostra como tratar configuração em Go de forma idiomática — do `os.Getenv` ao [Viper](https://github.com/spf13/viper) — seguindo o princípio **twelve-factor**, com defaults sensatos, validação fail-fast e segredos fora do repositório.

O texto complementa o [guia de graceful shutdown em produção](/blog/graceful-shutdown-go-producao/) (que precisa saber por quanto tempo esperar antes de derrubar conexões), o [guia de context, timeout e cancelamento](/blog/context-timeout-cancelamento-go/) (limites que vêm da configuração), o [guia de logging estruturado com slog](/blog/slog-go-logging-estruturado/) (nível de log é configuração) e o [guia de health checks](/blog/health-checks-go-liveness-readiness-startup/). Se você está montando uma API, leia também o [guia de frameworks HTTP em Go](/blog/frameworks-http-go-gin-echo-fiber-chi/) e o [guia de APIs REST](/aprenda/api-rest-go/).

## O problema: configuração não é código

A armadilha clássica do iniciante é escrever `const dbURL = "postgres://localhost:5432/meudb"` direto no código. Isso funciona no seu notebook e quebra em produção, onde o banco tem outra URL, outra senha e outra porta. Pior: vira commit no Git, expondo segredos e travando o binário a um ambiente só.

O **twelve-factor app**, metodologia que orientou o design de aplicações modernas, é direto sobre isso: *store config in the environment*. Configuração é tudo que muda entre ambientes (desenvolvimento, staging, produção) — credenciais, hosts, portas, flags de feature. Código é tudo que não muda. Misturar os dois torna o deploy frágil e inseguro.

A consequência prática é que **o mesmo binário compilado roda em qualquer ambiente**, mudando apenas as variáveis de ambiente e os arquivos de configuração injetados. Isso é o que torna o [deploy com Docker](/aprenda/golang-docker/) e o [deploy no Kubernetes](/tutoriais/go-kubernetes/) previsíveis: o artefato é imutável, a configuração é externa.

## Variáveis de ambiente: a base

A forma mais simples e portátil de configurar um serviço Go é ler variáveis de ambiente com a biblioteca padrão. `os.Getenv` devolve uma string:

```go
package main

import (
	"fmt"
	"os"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	fmt.Println("ouvindo na porta", port)
}
```

Há uma sutileza importante: `os.Getenv` devolve string vazia tanto quando a variável existe e está vazia quanto quando ela não existe. Para distinguir os dois casos, use `os.LookupEnv`:

```go
port, ok := os.LookupEnv("PORT")
if !ok {
	port = "8080" // variável não definida: usa o padrão
}
```

Essa distinção importa quando uma variável vazia significa algo diferente de ausência (por exemplo, um campo de feature flag opcional). Para um ou dois valores, a biblioteca padrão basta. Conforme o número de configurações cresce, no entanto, três problemas aparecem: conversão manual de tipos (string para `int`, `time.Duration`, `bool`), ausência de defaults declarativos e erro silencioso quando uma variável obrigatória não está definida. É aí que entra o `envconfig`.

## Envconfig: structs tipadas a partir do ambiente

O pacote `github.com/kelseyhightower/envconfig` resolve os três problemas de uma vez. Você declara uma struct e o envconfig a popula a partir das variáveis de ambiente, com defaults, tipos e validação de campos obrigatórios:

```go
package config

import (
	"time"

	"github.com/kelseyhightower/envconfig"
)

type Config struct {
	Port           int           `envconfig:"PORT" default:"8080"`
	DatabaseURL    string        `envconfig:"DATABASE_URL" required:"true"`
	JWTSecret      string        `envconfig:"JWT_SECRET" required:"true"`
	RequestTimeout time.Duration `envconfig:"REQUEST_TIMEOUT" default:"30s"`
	MaxConnections int           `envconfig:"MAX_CONNECTIONS" default:"100"`
}

func Load() (Config, error) {
	var cfg Config
	if err := envconfig.Process("", &cfg); err != nil {
		return Config{}, err
	}
	return cfg, nil
}
```

A chamada `envconfig.Process` retorna erro automaticamente se um campo `required:"true"` não estiver definido — falha rápida e clara na inicialização, em vez de comportamento estranho minutos depois. O `time.Duration` é parseado a partir de strings como `30s` ou `5m`, e os `int`/`bool` são convertidos com segurança.

No `main`, carregue a configuração uma vez e falhe cedo:

```go
func main() {
	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("configuração inválida: %v", err)
	}
	// ... passa cfg para o resto da aplicação
}
```

Essa abordagem casa perfeitamente com [clean architecture sem overengineering](/blog/clean-architecture-go-sem-overengineering/): a `Config` é um valor de domínio injetado onde precisa, sem globals espalhados. Para muitos serviços, o `envconfig` é tudo que você precisa.

## Viper: o canivete suíço da configuração

Quando a configuração precisa vir de várias fontes — um arquivo YAML, variáveis de ambiente, flags de linha de comando e defaults de código — o [Viper](https://github.com/spf13/viper) é a ferramenta padrão do ecossistema. Ele lê configuração em camadas, com precedência clara: a última definição vence.

```go
func Load() (*Config, error) {
	v := viper.New()

	v.SetConfigName("config")
	v.SetConfigType("yaml")
	v.AddConfigPath(".")
	v.AddConfigPath("/etc/myapp")

	v.SetEnvPrefix("MYAPP")
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	v.AutomaticEnv()

	v.SetDefault("server.port", 8080)
	v.SetDefault("server.shutdownTimeout", "30s")
	v.SetDefault("database.maxConns", 25)

	if err := v.ReadInConfig(); err != nil {
		return nil, fmt.Errorf("ler config: %w", err)
	}

	var cfg Config
	if err := v.Unmarshal(&cfg); err != nil {
		return nil, fmt.Errorf("unmarshal config: %w", err)
	}
	return &cfg, nil
}
```

O `v.Unmarshal(&cfg)` popula uma struct tipada a partir do mapa interno do Viper, com mapeamento automático entre as chaves do YAML e os campos da struct via tags ou nomes. A `Config` fica organizada por seção:

```go
type Config struct {
	Server   ServerConfig   `mapstructure:"server"`
	Database DatabaseConfig `mapstructure:"database"`
}

type ServerConfig struct {
	Port            int           `mapstructure:"port"`
	ShutdownTimeout time.Duration `mapstructure:"shutdownTimeout"`
}

type DatabaseConfig struct {
	URL      string `mapstructure:"url"`
	MaxConns int    `mapstructure:"maxConns"`
}
```

O arquivo `config.yaml` correspondente:

```yaml
server:
  port: 8080
  shutdownTimeout: 30s
database:
  url: "postgres://user:pass@localhost:5432/myapp?sslmode=disable"
  maxConns: 25
```

A combinação de `AutomaticEnv` com `SetEnvPrefix` permite que a variável `MYAPP_SERVER_PORT` sobrescreva `server.port` do YAML. Assim, você versiona defaults razoáveis no arquivo e injeta o que muda por ambiente — exatamente o que o [deploy em containers](/aprenda/golang-docker/) espera. O `SetEnvKeyReplacer` é o detalhe que conecta o ponto do YAML (`server.port`) ao underscore da variável (`SERVER_PORT`).

## Defaults, validação e fail-fast

Defaults bons são o que mantém um desenvolvedor novo rodando o projeto em minutos, mas eles têm um limite: defaults servem para desenvolvimento, não para segurança em produção. Um segredo nunca deve ter default. A regra prática é separar campos em três grupos:

| Tipo de campo | Exemplo | Default | Obrigatório |
|---|---|---|---|
| Operacional | porta, timeout, pool | Sim | Não |
| Conexão | URL de banco | Em dev | Sim em prod |
| Segredo | JWT secret, API key | Nunca | Sim |

Depois de carregar a configuração, valide a consistência antes de subir o servidor. O Viper não valida regras de negócio; faça isso em código explícito:

```go
func (c *Config) Validate() error {
	if c.Server.Port < 1 || c.Server.Port > 65535 {
		return errors.New("server.port deve estar entre 1 e 65535")
	}
	if c.Database.URL == "" {
		return errors.New("database.url é obrigatório")
	}
	if c.Database.MaxConns < 1 {
		return errors.New("database.maxConns deve ser positivo")
	}
	return nil
}
```

Falhar na inicialização é infinitamente melhor do que falhar no meio de uma requisição. Combine essa validação com [logging estruturado via slog](/blog/slog-go-logging-estruturado/) para registrar a configuração carregada (menos os segredos) e facilitar o diagnóstico.

## Configuração por ambiente

A forma mais limpa de lidar com múltiplos ambientes não é ter vários binários, mas sim um binário e várias fontes de configuração. Um padrão comum é ter um `config.yaml` com defaults versionado no repositório e sobrescrever via variáveis de ambiente o que for específico de cada ambiente:

```bash
# Desenvolvimento
DATABASE_URL=postgres://localhost:5432/myapp_dev LOG_LEVEL=debug ./myapp

# Produção
DATABASE_URL=postgres://prod-host:5432/myapp LOG_LEVEL=info ./myapp
```

Evite a tentação de versionar `config.prod.yaml` no Git com credenciais reais — é a origem mais comum de vazamentos. Se precisar de arquivos por ambiente, injete-os pelo orquestrador (ConfigMaps do Kubernetes, por exemplo) e nunca os commite.

## Segredos: o que nunca vai no config

A linha que separa configuração de segredo é simples: se vazaria em um log, é segredo. Senhas de banco, chaves de API, tokens JWT e credenciais de provedores de nuvem são segredos. Eles não devem ir em arquivos versionados nem em logs.

Em produção, a boa prática é injetar segredos pelo orquestrador: **Secrets do Kubernetes**, **AWS Secrets Manager**, **Google Secret Manager** ou **HashiCorp Vault**. Todos entregam segredos como variáveis de ambiente ou arquivos efêmeros no container, com rotação controlada e auditoria. A aplicação Go lê do ambiente do mesmo jeito que lê qualquer outra variável — a diferença está na origem segura.

```go
// Lendo um segredo injetado como variável de ambiente pelo orquestrador
jwtSecret := os.Getenv("JWT_SECRET")
if jwtSecret == "" {
	log.Fatal("JWT_SECRET não definido — verifique o Secrets do namespace")
}
```

Para autenticação que depende desses segredos, veja o [guia de autenticação e autorização em APIs Go](/blog/autenticacao-autorizacao-go-apis/). E nunca, nunca mesmo, coloque um segredo de verdade em um exemplo de código ou em um comentário — o Git lembra de tudo.

## Recarregamento a quente

O Viper consegue detectar mudanças no arquivo de configuração em tempo de execução e atualizar a cópia em memória:

```go
v.OnConfigChange(func(e fsnotify.Event) {
	log.Info("configuração recarregada", "file", e.Name)
	// reponderar a struct e reaplicar onde for necessário
})
v.WatchConfig()
```

Isso é útil para mexer em nível de log ou em limites operacionais sem reiniciar. Mas há uma pegadinha importante: o recarregamento só atualiza a *leitura* do arquivo. Conexões de banco abertas, pools e clientes HTTP **não se recriam sozinhos**. Mudanças estruturais (trocar a URL do banco, por exemplo) exigem reaplicar a configuração no objeto que a usa ou, mais seguro, reiniciar o serviço de forma controlada — o que leva direto ao [graceful shutdown](/blog/graceful-shutdown-go-producao/).

Para a maioria dos serviços, a complexidade do recarregamento a quente não compensa: prefira reiniciar o container em um deploy normal. Reserve o hot reload para casos em que a reconfiguração sem downtime traz valor real, como ajustar limites de [rate limiting](/blog/rate-limiting-go-api-producao/) ou nível de log sob carga.

## Viper ou envconfig: qual usar

A escolha depende do número de fontes de configuração e da complexidade do serviço:

| Critério | envconfig | Viper |
|---|---|---|
| Origem da configuração | Só variáveis de ambiente | YAML, JSON, env, flags, etcd |
| Defaults declarativos | Sim, via tags | Sim, via `SetDefault` |
| Tipagem forte | Sim (struct + tags) | Sim (via `Unmarshal`) |
| Recarregamento a quente | Não | Sim (`WatchConfig`) |
| Código e dependências | Mínimos | Mais pesado |
| Boa para microservices simples | Sim | Aceitável |
| Boa para apps com muito perfil | Com esforço | Sim |

A recomendação prática: comece com `envconfig` para serviços que vivem de variáveis de ambiente (o caso típico em containers e [serverless como AWS Lambda](/aprenda/go-aws-lambda/)). Migre para Viper quando precisar combinar arquivos estruturados com env, suportar múltiplos ambientes via arquivos ou habilitar hot reload. Não existe resposta universal — existe a ferramenta certa para o seu nível de complexidade.

## Armadilhas comuns

Três erros aparecem com frequência em configuração de serviços Go:

1. **Segredo no Git** — qualquer credencial real em `config.yaml` versionado é um vazamento esperando para acontecer. Use o histórico do Git para auditar: `git log -p -- config.yaml` não deve conter senhas.
2. **Default em campo obrigatório** — quando um segredo tem default "para facilitar", produção acaba rodando com um valor fraco. Campos de segredo devem ser `required:"true"` sem exceção.
3. **Configuração espalhada** — ler `os.Getenv` em dezenas de pontos do código cria um sistema impossível de auditar. Centralize em uma struct carregada na inicialização e passada explicitamente.

Para diagnosticar problemas de configuração em produção, registre a configuração carregada (com segredos mascarados) via slog e monitore variáveis não definidas que caíram em defaults perigosos. Configuração é parte da [observabilidade](/tutoriais/go-observability/) do serviço.

Se você trabalha em stacks múltiplas, vale comparar com o jeito que o <a href="https://python.dev.br/blog/pydantic-settings-configuracao-python/" target="_blank" rel="noopener noreferrer" onclick="umami.track(portfolio-site-click, { destination: python.dev.br })">Python Dev Brasil trata configuração tipada com Pydantic Settings</a>: a ideia de validar e tipar configuração a partir do ambiente é a mesma, só muda a linguagem.

## Perguntas frequentes

### Como ler variáveis de ambiente em Go?

Use `os.Getenv("NOME")` para ler uma variável como string, ou `os.LookupEnv("NOME")` quando você precisa distinguir uma variável vazia de uma variável não definida. Defina um valor padrão quando a leitura indicar que a variável não existe. Para validar tipos e defaults de forma automática, prefira o `envconfig`, que popula uma struct a partir das variáveis de ambiente com tags declarativas.

### Viper ou envconfig: qual usar?

Use `envconfig` quando a configuração vem só de variáveis de ambiente e você quer simplicidade e tipagem forte com pouco código. Use Viper quando você precisa combinar arquivos YAML/JSON, variáveis de ambiente, flags de linha de comando, defaults e recarregamento a quente na mesma aplicação. Muitos serviços só precisam de `envconfig`; o Viper vale a pena quando há várias fontes de configuração combinadas.

### Onde guardar segredos como senhas e tokens em Go?

Segredos não devem ir em arquivos de configuração versionados no Git. Em produção, injete segredos como variáveis de ambiente via orquestrador: Secrets do Kubernetes, AWS Secrets Manager, Google Secret Manager ou HashiCorp Vault. A aplicação Go lê do ambiente da mesma forma que lê qualquer outra configuração; a diferença é a origem controlada, rotacionável e auditável.

### O Viper faz recarregamento a quente de configuração?

Sim, com `WatchConfig` e `OnConfigChange` o Viper detecta mudanças no arquivo de configuração e atualiza a cópia em memória. Mas conexões de banco, clientes HTTP e pools não se recriam sozinhos: você precisa reponderar as mudanças e reaplicá-las, eventualmente reiniciando o serviço de forma controlada com [graceful shutdown](/blog/graceful-shutdown-go-producao/).

### O que é o princípio twelve-factor aplicado a configuração?

O twelve-factor determina que configuração deve ficar separada do código, injetada pelo ambiente em que a aplicação roda. Assim, o mesmo binário roda idêntico em desenvolvimento, staging e produção, mudando apenas as variáveis de ambiente e os segredos. Isso facilita deploy, testes e portabilidade entre provedores de nuvem.

## Conclusão

Configuração bem feita é invisível: a aplicação sobe em qualquer ambiente com as variáveis certas, falha cedo quando algo obrigatório falta e nunca expõe segredos. Comece com a biblioteca padrão para casos simples, adote `envconfig` para tipagem e defaults declarativos, e recorra ao Viper quando precisar combinar múltiplas fontes de configuração. Em todos os casos, mantenha a regra do twelve-factor: configuração fora do código, segredos fora do Git, e um único binário que roda em qualquer lugar.

Para aprofundar, explore o [roadmap de Go para 2026](/aprenda/roadmap-go-2026/), confira [vagas de Go no Brasil](/vagas/) e entenda a faixa de [salários de desenvolvedor Go](/carreira/salarios-go-brasil/) no mercado brasileiro. O próximo passo natural depois de dominar configuração é arquitetar a aplicação de ponta a ponta — leia o [guia de Go para backend](/aprenda/golang-para-backend/) e o de [clean architecture sem overengineering](/blog/clean-architecture-go-sem-overengineering/) para amarrar configuração, módulos e casos de uso sem cair em complexidade desnecessária.
