Managing Environment Files in System Administration

Manage environment files in system administration with best practices for secure handling and simple workflows. Practical tips for Linux and DevOps beginners to organize, version, and protect env vars.

Managing Environment Files in System Administration

Short introduction: Environment files are the simple text files that hold configuration values your services and scripts read at runtime. Managing them cleanly makes deployments predictable, reduces secrets leakage, and prevents "works on my machine" problems.

Why manage environment files?

Environment files centralize configuration, separate secrets from code, and make it easy to switch settings between environments (development, staging, production). They can live in several places (application root .env, /etc/environment, systemd unit files), and each has different semantics and security implications.

Practical reasons to manage them:

  • Consistency: same variables available across sessions and services.
  • Auditability: you can track changes and rotations.
  • Security: control access to secrets with file permissions and vaults.

Example: exporting variables for a shell session

# temporary: sets VARIABLE for this session only
export APP_MODE=production
echo $APP_MODE
# permanent: add to ~/.bashrc or a .env file and source it
echo 'APP_MODE=production' >> ~/.bashrc
source ~/.bashrc

Common formats and locations

There’s no single standard; pick one that fits your stack.

  • .env (key=value) — common for 12-factor apps and local development.
  • /etc/environment — system-wide key=value (no shell expansions).
  • /etc/profile.d/*.sh — scripts that can export variables for interactive shells.
  • systemd unit Environment= or EnvironmentFile= — for services managed by systemd.

Examples of file snippets:

# .env (simple key=value)
DATABASE_URL="postgres://user:pass@db.example.com:5432/appdb"
REDIS_HOST=redis
REDIS_PORT=6379

# /etc/environment (no export, no shell syntax)
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
LANG="en_US.UTF-8"

Load a systemd EnvironmentFile in a unit:

# /etc/systemd/system/myapp.service.d/env.conf
[Service]
EnvironmentFile=/etc/myapp.env

After editing, reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Commands table

Below are common commands and what they do when managing environment files.

Command Purpose
export VAR=value Set an environment variable in the current shell session
source /path/to/file Apply variables defined in a file to the current shell
env List current environment variables
env grep KEY
sudo systemctl daemon-reload Tell systemd to reload unit files after editing
sudo systemctl restart service Restart a systemd service so it picks up new env
chmod 600 /path/to/.env Restrict access to an env file (owner read/write)
git update-index --assume-unchanged .env Stop tracking local env changes (not secure long-term)
gpg -c secrets.env Symmetric encrypt a file with GPG
ansible-vault encrypt secrets.yml Encrypt secrets for Ansible

Short examples:

# check if an environment variable is set
env | grep -i DATABASE_URL || echo "DATABASE_URL not set"

# restrict .env permissions
chmod 600 /srv/myapp/.env
ls -l /srv/myapp/.env

Loading and validating environment files

How you load an env file matters: shells handle quoting/expansion differently from system-wide loaders.

  • For interactive shells and scripts:
    • Use source /path/to/.env to export variables into the current shell (ensure file uses safe key=value syntax).
  • For systemd services:
    • Use EnvironmentFile= and place plain key=value lines (no exports or expansions).
  • For Docker:
    • Use --env-file or docker-compose env_file to pass variables to containers.

Validation techniques:

For secret-containing files, avoid echoing them into logs. When testing, dump variable names only:

# list variable names only (not values)
grep -oE '^[A-Za-z_][A-Za-z0-9_]*' .env

More robust: use dotenv-linter or shellcheck for scripts

# install dotenv-linter (example for Linux using cargo if Rust available)
cargo install dotenv-linter
dotenv-linter .env

Basic sanity check (no spaces around =, no unescaped quotes):

# simple validator: prints invalid lines
grep -nE '^[A-Za-z_][A-Za-z0-9_]*=' .env || echo "No valid lines found"
# detect lines with spaces around =
grep -nE '\s=\s' .env && echo "Fix spaces around = in .env"

Managing secrets and version control

Secrets must be handled differently than non-sensitive env variables.

Best practices:

  • Never commit plain .env files with secrets into git.
  • Use a secrets manager (HashiCorp Vault, AWS SSM Parameter Store, AWS Secrets Manager, GCP Secret Manager) for production.
  • Keep a template (.env.example) with keys but placeholder values to show required variables.

Examples: ignore .env in Git and provide a template

# .gitignore
.env

# Create a template
cat > .env.example <<'EOF'
DATABASE_URL=postgres://user:password@host:5432/dbname
API_KEY=your_api_key_here
EOF

git add .env.example
git commit -m "Add env template"

Encrypting files:

# symmetric gpg encryption of a secrets file
gpg -c secrets.env
# decrypt when needed
gpg -o secrets.env -d secrets.env.gpg

Using Ansible Vault:

ansible-vault encrypt group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml

When deploying:

  • Inject variables at runtime using CI/CD secrets and runtime environment configuration.
  • For systemd, consider Environment= entries created by your deployment tooling rather than storing secrets in /etc.

Common Pitfalls

  • Leaving secrets in git: accidental commits of .env expose secrets to any collaborator or public mirrors.
  • Mixing shell syntax with system loaders: systemd and /etc/environment do not support "export" or variable expansion; using them causes silent failures.
  • Loose file permissions: env files readable by all users can lead to privilege escalation or secret leakage.

Example showing a problematic .env for systemd:

# BAD for systemd: systemd doesn't interpret "export" or expansions
export DB_PASS="pa$$word"
CONFIG_DIR="$HOME/.config/myapp"

Fix by making plain key=value and restricting permissions:

DB_PASS="pa$$word"
CONFIG_DIR=/home/myuser/.config/myapp
chmod 600 /etc/myapp.env

Next Steps

  • Adopt a secrets management workflow for production (Vault, cloud provider secrets, or encrypted Ansible/Vault).
  • Create and enforce a deployment convention: .env.example for templates, CI secrets for injection, and strict file permissions (chmod 600) for any file with secrets.

Audit your repositories for exposed env files: run git grep -n -- .env and remove/rotate secrets if found.

git grep -n -- '\.env' || echo "No .env references found"

By following these practical steps—using the right file for the right scope, validating and restricting access, and integrating secrets management—you’ll make configuration predictable and safer across environments.

👉 Explore more IT books and guides at dargslan.com.