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.
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.