Truques para criar arquivos de configuração dinâmicos com envsubst

1. Introdução ao envsubst e substituição de variáveis

O envsubst é uma ferramenta presente no pacote gettext do Linux que permite substituir variáveis de ambiente em arquivos de texto de forma simples e eficiente. Diferentemente de ferramentas como sed ou awk, que exigem expressões regulares complexas, o envsubst trabalha diretamente com o formato padrão de variáveis do shell ($VAR e ${VAR}), tornando o processo mais intuitivo e menos propenso a erros.

A sintaxe básica é direta:

envsubst < template.txt > config.txt

Este comando lê o arquivo template.txt, substitui todas as variáveis de ambiente encontradas pelos seus valores atuais e escreve o resultado em config.txt. O comportamento padrão é substituir todas as variáveis no formato $VAR e ${VAR}.

2. Preparação de templates para substituição

Para criar templates eficientes, utilize placeholders claros e consistentes. A estrutura básica de um template pode ser:

# template.conf
DATABASE_HOST=$DB_HOST
DATABASE_PORT=${DB_PORT}
DATABASE_USER=$DB_USER
DATABASE_PASSWORD=${DB_PASSWORD}
LOG_LEVEL=${LOG_LEVEL:-info}

Note que é possível definir valores padrão usando a sintaxe ${VAR:-default}. Isso garante que, mesmo que a variável de ambiente não esteja definida, o template terá um valor funcional.

Para evitar conflitos com caracteres especiais, lembre-se:
- O cifrão ($) sempre inicia uma variável
- Use aspas simples para evitar expansão acidental no shell
- Barras invertidas (\) podem escapar caracteres em alguns contextos

3. Controle granular com a opção SHELL-FORMAT

Uma das funcionalidades mais poderosas do envsubst é a capacidade de filtrar quais variáveis serão substituídas usando a opção SHELL-FORMAT:

envsubst '$DB_HOST $DB_PORT' < template.conf > config.conf

Neste exemplo, apenas $DB_HOST e $DB_PORT serão substituídos. Todas as outras variáveis (como $PATH ou $HOME) permanecerão literais no arquivo de saída. Isso é crucial quando seu template contém variáveis que não devem ser expandidas.

Exemplo prático para um arquivo de configuração de banco:

# template_db.conf
[Database]
Host=$DB_HOST
Port=${DB_PORT}
User=admin
Password=${DB_PASSWORD}
Path=$PATH
export DB_HOST=localhost
export DB_PORT=5432
envsubst '$DB_HOST $DB_PORT' < template_db.conf > db.conf

O resultado manterá ${DB_PASSWORD} e $PATH como literais.

4. Combinando envsubst com arquivos .env e export

Para projetos maiores, é comum utilizar arquivos .env para gerenciar variáveis. A combinação com envsubst é poderosa:

# Carregar variáveis do .env, ignorando comentários
export $(grep -v '^#' .env | xargs)

# Gerar configuração
envsubst < template.conf > config.conf

Uma abordagem mais robusta usando set -a:

set -a
source .env
set +a

envsubst < template.conf > config.conf

O comando set -a faz com que todas as variáveis atribuídas sejam automaticamente exportadas para o ambiente, eliminando a necessidade de export manual para cada variável.

5. Geração de múltiplos arquivos a partir de um único template

Um dos cenários mais úteis é gerar configurações para diferentes ambientes a partir de um template único:

# Template: template.yml
app:
  name: $APP_NAME
  version: ${APP_VERSION:-1.0.0}
  environment: $ENV
  database:
    host: $DB_HOST
    port: ${DB_PORT:-5432}

Arquivos de ambiente:

# dev.env
APP_NAME=myapp-dev
APP_VERSION=2.0.0-beta
ENV=development
DB_HOST=localhost
DB_PORT=5432
# prod.env
APP_NAME=myapp
ENV=production
DB_HOST=db.prod.example.com

Gerando as configurações:

for env in dev prod; do
  set -a
  source "$env.env"
  set +a
  envsubst < template.yml > "${env}.yml"
done

6. Tratamento de variáveis aninhadas e valores complexos

Ao trabalhar com JSON e YAML, é necessário cuidado especial com aspas e caracteres de escape:

# template.json
{
  "service": {
    "name": "$SERVICE_NAME",
    "host": "${SERVICE_HOST}",
    "config": "${SERVICE_CONFIG}"
  }
}

Para valores contendo espaços ou caracteres especiais, use aspas duplas no template:

export SERVICE_CONFIG='{"timeout": 30, "retries": 3}'
envsubst < template.json > config.json

A dica de usar ${VAR:-default} é especialmente útil para evitar falhas quando variáveis não estão definidas:

export DB_HOST=${DB_HOST:-localhost}
export DB_PORT=${DB_PORT:-3306}

7. Integração com Docker e containers

O envsubst é amplamente utilizado em imagens Docker para gerar configurações em tempo de execução. Um exemplo clássico com Nginx:

# entrypoint.sh
#!/bin/bash
set -e

# Gerar configuração do Nginx
envsubst '$NGINX_HOST $NGINX_PORT' < /etc/nginx/nginx.template > /etc/nginx/nginx.conf

# Limpar variáveis sensíveis após uso
unset NGINX_HOST NGINX_PORT

# Iniciar Nginx
exec nginx -g 'daemon off;'

No Dockerfile:

COPY nginx.template /etc/nginx/nginx.template
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Boas práticas importantes:
- Sempre limpe variáveis sensíveis após a substituição
- Use exec no final para garantir que o processo principal receba sinais corretamente
- Mantenha templates versionados e arquivos gerados no .gitignore

8. Depuração e boas práticas

Para verificar se todas as variáveis foram substituídas corretamente:

# Verificar variáveis não substituídas
grep '\$' config_saida.conf

# Usar set -u para evitar substituições silenciosas
set -u
envsubst < template.conf > config.conf

O comando set -u faz com que o shell interrompa a execução se encontrar uma variável não definida, evitando substituições silenciosas por strings vazias.

Versionamento de templates:

# .gitignore
*.conf
!*.template

Mantenha apenas os templates no versionamento e gere os arquivos de configuração durante o deployment. Isso garante que as configurações específicas de cada ambiente não poluam o repositório.

Referências