6 pontos por GN⁺ 2025-10-15 | 2 comentários | Compartilhar no WhatsApp
  • Embora o software tenha evoluído rapidamente, o sistema de variáveis de ambiente do sistema operacional ainda mantém uma estrutura de décadas atrás
  • As variáveis de ambiente têm a forma de um dicionário global de strings, uma estrutura simples sem namespace nem tipos
  • No Linux, as variáveis de ambiente são passadas do processo pai para o filho por meio da chamada de sistema execve
  • Bash, glibc, Python e outros gerenciam variáveis de ambiente respectivamente como hash map, array e wrapper de dicionário
  • O padrão POSIX não exige apenas letras maiúsculas nos nomes e, na prática, há regras flexíveis, como a recomendação de usar nomes em minúsculas em certos casos

O que são variáveis de ambiente

  • Embora as linguagens de programação tenham evoluído rapidamente, a estrutura base de execução de processos oferecida pelo sistema operacional, especialmente a parte de variáveis de ambiente, quase não mudou
  • Ao executar uma aplicação, se você precisa passar parâmetros de runtime sem arquivos separados nem IPC, na prática acaba recorrendo a uma interface baseada em variáveis de ambiente
  • As variáveis de ambiente funcionam como um dicionário plano de strings, sem namespace e sem tipos

Estrutura de criação e transmissão das variáveis de ambiente

  • As variáveis de ambiente são o método tradicional de transmitir valores entre processos, sendo enviadas junto quando o processo pai executa o processo filho
    • Ou seja, trata-se de uma estrutura herdada do processo pai para o filho
  • No Linux, a system call execve recebe como argumentos o executável, os argumentos e o array de variáveis de ambiente (envp)
    • Exemplo de comando executado: ls -lah
      • filename: /usr/bin/ls
      • argv: ['ls', '-lah']
      • envp: ['PATH=...','USER=...']
  • O processo pai pode passar ao filho o ambiente existente como está ou montar um ambiente totalmente novo
    • Quase todas as ferramentas (Bash, subprocess.run do Python, biblioteca C execl etc.) repassam as variáveis de ambiente como estão
    • Como exceção, algumas ferramentas, como login, constroem um novo ambiente

Onde as variáveis de ambiente são armazenadas e como são tratadas internamente

  • Ao iniciar um programa, o kernel armazena as variáveis de ambiente na stack, no formato de strings terminadas em null
  • Esses dados são difíceis de modificar diretamente pelo programa e, em geral, são copiados para estruturas próprias dentro da aplicação
  • Forma de armazenamento das variáveis de ambiente em cada linguagem e shell
    • Bash: gerencia em um hash map (dicionário) com estrutura em pilha
      • A cada chamada de função, cria um mapa de escopo local
      • Apenas variáveis com export são passadas ao processo filho
      • Variáveis declaradas com local também podem ser passadas ao processo filho via export
        • Ex.: com export PATH, uma alteração local é refletida no filho sem afetar o escopo global
    • glibc (biblioteca C): gerencia environ como uma estrutura de array dinâmico por meio de putenv e getenv
      • Como é uma estrutura de array, tanto consulta quanto alteração têm complexidade linear de tempo
      • Portanto, não é adequada para armazenar dados que exijam alto desempenho
    • Python: internamente expõe isso como um dicionário via os.environ, mas na prática está integrado ao array environ da biblioteca C
      • Ao alterar um valor em os.environ, os.putenv é chamado e a mudança também é refletida na biblioteca C
      • No sentido inverso, não há sincronização, portanto existe unidirecionalidade

Formato e faixa permitida das variáveis de ambiente

  • O kernel do Linux e a glibc são muito permissivos quanto ao formato das variáveis de ambiente
    • Pode haver nomes duplicados com vários valores para o mesmo nome
    • Também é possível registrar entradas sem = e não há restrição a caracteres especiais como emoji
  • Limites de tamanho disponíveis
    • Variável individual: 128 KiB (geralmente em ambiente x64)
    • Soma total: 2 MiB (compartilhada com os argumentos da linha de comando)
    • As variáveis de ambiente são limitadas para não ultrapassar 1/4 do espaço da stack

Peculiaridades e edge cases das variáveis de ambiente

  • No Bash, variáveis de ambiente estranhas (nomes duplicados, entradas sem = etc.) fazem com que nomes duplicados sejam removidos e itens anormais sejam ignorados
  • Se o nome da variável contém espaços, o Bash não consegue referenciá-la pelo nome, mas ainda assim ela pode ser passada para processos filhos
    • Por exemplo, Nushell e Python conseguem criar variáveis com nomes contendo espaços
    • O Bash gerencia esses itens armazenando-os em um hash map separado (invalid_env)

Formato padrão das variáveis de ambiente e regras para nomes

  • O padrão POSIX considera uma entrada como variável desde que o nome não contenha sinal de igual (=)
    • Recomendação oficial: o nome deve permitir apenas letras maiúsculas, números e underscore (o primeiro caractere não pode ser número)
    • Variáveis em minúsculas são destinadas a namespaces específicos de aplicações
    • Ferramentas padrão usam apenas maiúsculas, mas o uso de variáveis em minúsculas também é permitido
  • Na prática, desenvolvedores costumam nomear usando o estilo ALL_UPPERCASE
  • Regra recomendada: usar a regex ^[A-Z_][A-Z0-9_]*$ para nomes de variáveis e UTF-8 para os valores
    • Se houver preocupação com exceções ou compatibilidade, recomenda-se usar o Portable Character Set (ASCII) do POSIX

Conclusão

  • As variáveis de ambiente continuam sendo uma interface antiga, mas essencial, servindo como camada de fronteira entre sistema operacional e aplicações
  • Apesar das limitações estruturais, Bash, C e Python continuam a utilizá-las, cada um encapsulando-as de forma diferente
  • Em sistemas modernos, torna-se cada vez mais necessária uma forma de gerenciamento de configuração com namespaces claros e sistema de tipos

2 comentários

 
howudoin 2025-10-15

À primeira vista, parecia estar perdendo importância, mas com a chegada do Docker e da nuvem, tornou-se inevitável novamente.

 
GN⁺ 2025-10-15
Comentários do Hacker News
  • Trabalho com SRE/Sysadmin/DevOps/Whatever, e embora no blog eu tenha falado de forma leve sobre padronização de variáveis de ambiente, quero apontar que as alternativas também acabam provocando frustrações parecidas, especialmente quando segredos estão envolvidos
    Uma arquitetura em que a aplicação acessa um cofre específico, como Hashicorp Vault/OpenBao/Secrets Manager, rapidamente cria um forte lock-in de fornecedor, e a substituição se espalha até o nível das bibliotecas, ficando muito difícil
    A disponibilidade do Vault passa a ser extremamente crítica, e quando upgrades ou manutenção são necessários, a equipe de operações fica em grande apuro
    Quando se passa informação sensível por arquivo de configuração, surge a dificuldade de como armazenar esse segredo, porque arquivos de config geralmente ficam em caminhos públicos
    No fim, você acaba dependendo de uma destas opções: "substituir por template antes de um sistema privilegiado entregar ao app" ou "armazenar o arquivo de config inteiro no cofre e entregar ao app"
    O trabalho com templates é propenso a erros, e mover o arquivo de config inteiro para o cofre também gera estresse, porque sempre existe o risco de alguém fazer upload errado
    Hoje em dia a maioria dos sistemas roda sobre contêineres, e a menos que a empresa cuide muito bem da infraestrutura, os arquivos de config sempre acabam em lugares estranhos, o que torna o processo de montagem ainda mais confuso e cheio de erros
    Seja JSON/YAML/TOML ou qualquer outro formato, bugs peculiares são rotina, como o problema da Noruega no YAML
    Já vi gente recebendo segredos via Kubernetes Secrets API, mas isso também esbarra em lock-in forte
    A menos que você esteja projetando algo especificamente como um sistema no estilo operator, eu não recomendaria esse caminho com entusiasmo
    Também já vi problemas surgirem no processo de configurar variáveis de ambiente via subprocesso, mas hoje em dia acho que as equipes preferem sistemas baseados em message bus, que são mais robustos e permitem escalabilidade independente

    • No nosso time, já tivemos a experiência de criar uma biblioteca leve e genérica de secrets, usando apenas backends específicos de fornecedores, como AWS Secrets Manager, em esquema de plugin
      Era possível configurar cache local e opções de bypass de cache por parâmetro, então a lógica realmente dependente do fornecedor ficava só no backend, e tanto a biblioteca quanto a aplicação podiam permanecer independentes do fornecedor
      Quando migramos para Vault, bastou adicionar mais um backend e mudar a configuração, e funcionou sem maiores problemas

    • Tenho curiosidade sobre por que a API de secrets do Kubernetes é vista como um problema de lock-in
      Foi algum caso de tentar usar deployment yaml para um propósito que não fosse deploy em Kubernetes?
      Na maioria dos apps, você pode montar o secret no contêiner e então injetá-lo na aplicação como variável de ambiente ou arquivo json, lendo e escrevendo de forma independente do ambiente
      Pelo que sei, a criptografia do backend etcd também pode ser configurada com KMS

    • Não entendo muito bem por que receber segredos via Kubernetes Secrets API seria lock-in
      Em essência, os secrets do K8s não são armazenados criptografados por padrão, então, na minha visão, isso só faz sentido se você (0) já usa K8s, (1) configurou a criptografia no control plane e (2) obrigatoriamente usa uma solução adicional como CSI driver
      E o Secret Store CSI Driver suporta vários backends, como Conjur, então na verdade seria o oposto de lock-in

    • É por isso que ainda continuo usando config principalmente com env vars e dotenv
      A estrutura de configuração baseada em variáveis de ambiente é simples demais, e também é muito compatível com várias ferramentas, incluindo gerenciadores de segredos
      Nos últimos anos comecei a me interessar aos poucos por sops baseado em YAML
      YAML é realmente intuitivo para expressar a estrutura de configuração da aplicação, e com sops é fácil criptografar e gerenciar só partes do arquivo
      Claro que gerenciar chaves GPG pode ser complicado, mas dá para resolver isso com Vault ou OpenBao
      Só que aí aparece de novo a questão do lock-in de fornecedor, embora o OpenBao pareça um pouco menos problemático nesse aspecto

    • Também dá para receber variáveis de ambiente como resultado da execução de um comando, então é possível fazer isso sem lock-in de fornecedor e sem processo de template

  • Mais uma curiosidade interessante: setenv() é fundamentalmente quebrado no POSIX, então acho que nunca deveria ser usado em código de biblioteca
    Mesmo em código de aplicação, deve ser o último recurso e só pode ser usado antes de criar threads
    getenv() retorna diretamente um ponteiro para a variável de ambiente original, então quando setenv() sobrescreve a variável, não existe nenhum mecanismo de proteção
    É preciso ter muito cuidado

    • Acho que a forma correta de configurar variáveis de ambiente é com execve()
      Esse método só é apropriado quando a informação é passada por variáveis de ambiente no momento do exec() ou em torno dele

    • Não entendo por que alguém usaria setenv em código de biblioteca

    • O Solaris resolveu esse problema, mas o Linux ainda insiste na mesma abordagem

    • No NetBSD já existe há muito tempo uma alternativa segura chamada getenv_r(), e recentemente o FreeBSD também a adotou
      O macOS provavelmente deve seguir pelo mesmo caminho em breve
      Já houve tentativas de colocá-la no glibc ou no POSIX, mas foram rejeitadas
      Espero que, quando isso se espalhar por várias plataformas, acabe sendo aceito oficialmente algum dia
      Documentação do getenv_r no NetBSD
      Commit do FreeBSD

  • Variáveis de ambiente são frequentemente usadas para passar segredos, mas eu não acho que isso seja uma boa prática
    No Linux, todos os processos executados pelo mesmo usuário conseguem inspecionar as variáveis de ambiente uns dos outros
    Seja qual for o modelo de ameaça, isso preocupa bastante, especialmente em máquinas de desenvolvedores, onde há muitos processos rodando sob o mesmo usuário
    Esse problema fica ainda mais sério quando existem vários processos rodando fora do contêiner, como agentes de LLM
    Além disso, variáveis de ambiente normalmente são herdadas pelos processos filhos, então mesmo quando apenas um processo precisa do segredo, ele tende a ficar exposto de forma indiscriminada
    O systemd expõe variáveis de ambiente a todos os clientes do sistema via DBUS, e a documentação oficial alerta para não colocar segredos em variáveis de ambiente
    Se isso for verdade, significa que variáveis de ambiente configuradas em unidades só para root também podem ficar visíveis para usuários comuns, o que seria bem chocante para muitos administradores de sistemas
    No fim, acredito que a única saída para escapar tanto da exposição em variáveis de ambiente quanto em arquivos em texto puro é usar uma estrutura em que o gerenciador de segredos entrega o segredo por meio de arquivo temporário compartilhado, como fazem o op cli da 1Password, flask e terraform
    É assim que funciona o sistema de credentials do systemd. Mas o suporte ainda é pequeno
    Se alguém souber de uma boa forma de passar segredos sem usar variáveis de ambiente nem arquivos em texto puro, eu gostaria que compartilhasse
    No caso do cliente op da 1Password, por exemplo, como ele exige minha aprovação a cada sessão, sinto que é seguro para usar numa sessão de CLI; mesmo que algum processo malicioso invoque o binário op, ainda será necessária uma aprovação separada
    Agora o problema que sobra é: como passar esse segredo para o processo que realmente precisa dele? Parece que voltamos ao ponto de partida
    Link da documentação oficial do systemd sobre variáveis de ambiente

    • Desde algo em torno de 2012, variáveis de ambiente passaram a ser tão seguras quanto a memória comum
      Commit relacionado
      Para ler as variáveis de ambiente de outro processo, é obrigatório ter permissão de ptrace; e, se você já consegue ler via ptrace, na prática já consegue ler qualquer segredo, então essa preocupação não faz muito sentido
      Informação de linha de comando (cmdline) é outra história, mas variáveis de ambiente já não ficam mais expostas tão facilmente dessa forma

    • No modelo de segurança da maioria dos sistemas operacionais, rodar sob um mesmo usuário significa essencialmente entregar por completo todos os privilégios desse usuário
      Há exceções com recursos extras de segurança, como capsicum no FreeBSD, landlock no Linux, SELinux, AppArmor e integrity label no Windows, mas a maioria tem limitações claras
      No fim das contas, eu posso matar, parar ou depurar livremente meus próprios processos, e segredos dos processos que possuo sempre podem ser acessados com ptrace/process_vm_readv/ReadProcessMemory etc.
      Existem modelos de segurança completamente diferentes, como sistemas operacionais perfeitamente baseados em capability, mas a grande maioria segue esse modelo, então é preciso reconhecer seus limites e responsabilidades

    • Uma boa forma de passar segredos sem usar variáveis de ambiente nem arquivos em texto puro pode ser memfd_secret
      man page do memfd_secret
      Ainda não há muito suporte por linguagem ou framework, então talvez valha tentar via FFI, especialmente em Rust ou talvez também em Go
      Já cogitei criar um wrapper direto para PHP, mas desisti porque não queria chegar ao ponto de modificar o php-fpm
      Na prática, o ideal seria o gerenciador de processos abrir antes um descritor de arquivo de secret e passá-lo ao processo filho, para que ele pudesse usá-lo sem expor nada em memória e afins

    • O modelo clássico de segurança do Unix ainda é muito usado, ainda que com pequenas melhorias, mas mostra limites claros em ambientes baratos de computação e em cenários modernos
      Se você precisa esconder segredos de outros processos, o caminho correto é separá-los para rodar sob outro usuário
      Outra opção é acessar tudo remotamente, mas isso também traz desvantagens e complexidade

    • Hoje em dia, plataformas de contêineres tendem a recomendar o uso de variáveis de ambiente para passar config ou secrets
      Dentro do contêiner, ele é projetado de forma que outros processos não consigam inspecionar as variáveis de ambiente
      O fato de processos filhos herdarem variáveis de ambiente também é intencional no design, porque quem configura o ambiente com o valor do secret é justamente quem também configura esse ambiente
      Eu não vejo a maioria dessas preocupações como grandes problemas, mas, se necessário, posso discutir os pontos concretos

  • Muitos comentários estão focando no gerenciamento de segredos e seus problemas, mas também vale pensar um pouco nas vantagens das variáveis de ambiente
    Variáveis de ambiente são uma forma de "binding dinâmico de variáveis com escopo indefinido" que conecta estruturalmente processos Unix
    Em vez de compará-las diretamente com um simples arquivo de texto, vale lembrar que a razão de existir das variáveis de ambiente é o repasse de contexto para transmitir informações com segurança a processos filhos
    Quanto mais complexa a estrutura de processos — shells aninhados ou subprocessos de programas complicados, por exemplo — mais esse papel das variáveis de ambiente se destaca

  • Quero recomendar o Varlock como algo realmente útil
    Ele permite definir com clareza quais variáveis de ambiente são obrigatórias ou opcionais em um projeto, seus tipos de dados e até de onde vêm, o que facilita bastante a gestão
    Site oficial do Varlock

  • Pela experiência prática, um exemplo de como variáveis de ambiente podem ficar complexas: em uma empresa onde trabalhei, certa vez tentei depurar de onde vinha uma variável ENV específica e fiquei completamente perdido
    No começo achei que estivesse definida em algum lugar simples, como .bashrc, mas na verdade ela era configurada por pelo menos 10 camadas: nível da empresa inteira, região, divisão de negócios, time, indivíduo etc.
    No fim, só consegui rastrear tudo ligando flags de debug do bash e seguindo passo a passo onde ela era definida

    • Não sei se outras linguagens suportam algo parecido, mas o Node.js adicionou recentemente uma flag de linha de comando que rastreia com precisão acessos e alterações em variáveis de ambiente
      Documentação do Node.js --trace-env
      Como valores podem ser definidos ou alterados por muitas APIs diferentes, isso parece muito útil em depurações complexas

    • É um caso que faz pensar: "será que um namespace só não bastaria?"

  • Faz muito tempo que abandonei o uso de variáveis de ambiente
    Hoje eu mantenho um arquivo dmd.conf ao lado do compilador, e o próprio compilador lê esse arquivo diretamente

  • O problema mais grave das variáveis de ambiente é seu caráter implícito e opaco
    No mundo *nix, a maioria dos apps tende a depender de variáveis de ambiente
    Mesmo quando há suporte adicional a formas explícitas e transparentes de configuração, como arquivos de configuração, serviços remotos ou argumentos de linha de comando, o suporte a variáveis de ambiente continua sendo a tradição da área
    No fim, variáveis de ambiente também são um hashmap global, clonado e expandido para processos filhos; em 1979 isso talvez fosse um design razoável, mas hoje muitas vezes acaba sendo um veneno
    Por exemplo, o Kubernetes polui por padrão o ambiente do contêiner com variáveis de ambiente de "service link"
    Quando variáveis de ambiente esperadas pela aplicação entram em conflito com env vars padrão, a depuração fica extremamente difícil
    Documentação oficial do Kubernetes
    Além disso, sinto que há muitas práticas de manter estruturas legadas sem reflexão crítica, como /bin, /usr/bin, /lib, /usr/lib
    Referência: Q&A do Ubuntu sobre manutenção de diretórios legados

    • Combinações de teclas como hjkl também podem ser vistas como exemplo dessa tradição antiquada
      O hjkl do vi vem de um terminal burro de 40 anos atrás, que inclusive vendeu pouco
      (menos até que o Nokia N9)
  • Sempre me dá uma sensação de insegurança quando vou configurar variáveis de ambiente no Linux
    Não existe um método oficialmente padronizado que funcione do mesmo jeito em todas as distribuições, e mesmo seguindo instruções online tudo desaparece depois de reiniciar ou fechar o terminal
    Seria ótimo se houvesse um editor gráfico simples de variáveis de ambiente globais, como no Windows
    No Windows existe o incômodo de ter que abrir um novo terminal para as mudanças entrarem em vigor, mas fora isso sempre funciona bem

    • É natural que variáveis de ambiente não persistam entre sessões, então elas precisam ser escritas em algum lugar que seja executado novamente a cada sessão, como no login ou ao abrir o terminal
      No login, roda o .bash_profile; em sessões filhas, roda o .bashrc
      Se você der source do .bashrc dentro do .bash_profile e deixar a maior parte das configurações no .bashrc, fica fácil de gerenciar
      Se você não usa Bash, mas zsh/fish ou outro shell, precisa ajustar conforme esse shell
      O Linux não tem um GUI oficial e unificado de variáveis de ambiente que se aplique a todos os terminais
      Até daria para criar um GUI com parsing complexo, mas em geral é mais fácil editar em texto mesmo

    • Do meu ponto de vista, como alguém que usa Linux principalmente, o comportamento do Windows é que parece mais incômodo
      Aplicativos demais poluem as variáveis de ambiente, então quando algo falha frequentemente a confusão é porque $SOFTWARE estava sendo executado a partir de uma pasta estranha

    • Se você usa systemd, também dá para escrever KEY=VALUE em /etc/environment ou /etc/environment.d/
      Na prática, até daria para criar um GUI para esse método
      Mas variáveis de ambiente não podem ser injetadas em processos já em execução; é preciso reiniciar para refletir as mudanças, então há esse limite
      Referência da documentação oficial do systemd

    • Quadrinho xkcd Standards
      Mostra de forma divertida a situação em que já existem 14 formas concorrentes de configurar variáveis de ambiente no Linux, e alguém propõe "unificar tudo em uma só" — no dia seguinte passam a existir 15 padrões

  • Minha curiosidade favorita sobre variáveis de ambiente é que coisas como PS1, que todo mundo assume serem variáveis de ambiente, na verdade não são; são variáveis do shell
    Você nem consegue vê-las com o comando env