Passing Secrets and Settings
An environment variable is a dynamic named value that affects how running processes behave. They're the primary mechanism for injecting configuration into applications without modifying code.
The 12-Factor App methodology specifically calls out environment variables as the canonical way to configure applications across deployment environments.
# View all environment variables
$ env
HOME=/home/admin
USER=admin
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
SHELL=/bin/bash
LANG=en_US.UTF-8
EDITOR=vim
PAGER=lessViewing Variables
printenv — All Variables
$ printenv | head -20
LANG=en_US.UTF-8
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
HOME=/home/admin
LOGNAME=admin
PWD=/home/admin
SHELL=/bin/bash
TERM=xterm-256color
USER=admin
USERNAME=adminecho — Single Variable
# Print current user's home directory
$ echo $HOME
/home/admin
# Print the PATH variable (colon-separated list of directories)
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
# Print the current shell
$ echo $SHELL
/bin/bashenv — Run Command with Modified Environment
# Run a command with a temporary environment variable
$ TEMP_VAR=hello env | grep TEMP_VAR
TEMP_VAR=hello
# Note: the variable only exists for that one commandSetting Variables
Temporary Variables (Current Session Only)
# Create a variable (no spaces around =)
$ export DB_PASSWORD="super_secret_password"
# Verify it exists
$ echo $DB_PASSWORD
super_secret_password
# This variable disappears when you close the terminalPersistent Variables (Shell Profile)
Add to ~/.bashrc (per-user) or /etc/environment (system-wide):
# Add to ~/.bashrc
$ echo 'export APP_ENV="production"' >> ~/.bashrc
# Reload the profile
$ source ~/.bashrc
# or
$ . ~/.bashrc
# Verify it persisted
$ echo $APP_ENV
productionCommon Profile Files
| File | Scope | When It Runs |
|---|---|---|
/etc/environment | System-wide | All users at login |
/etc/profile | System-wide | All users at login |
~/.bashrc | User | Each bash session |
~/.bash_profile | User | Login shells only |
~/.profile | User | Login shells (fallback for sh) |
The PATH Variable
PATH is the most critical environment variable. It lists directories searched for executables when you type a command.
# View PATH (colon-separated)
$ echo $PATH
/home/admin/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
# Each directory is searched left to right
# First match wins — that's why /usr/local/bin usually comes first
# (so your locally-installed tools override system defaults)# Find where a command comes from
$ which python3
/usr/bin/python3
$ which nginx
/usr/sbin/nginx
# Find ALL locations of a command
$ which -a python3
/usr/bin/python3
/home/admin/.pyenv/shims/python3# Add a directory to PATH (temporary)
$ export PATH="/opt/myapp/bin:$PATH"
# Add to ~/.bashrc for persistence
$ echo 'export PATH="/opt/myapp/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrcIf an application says "command not found", check whether its directory is in
$PATH. This is the first thing to verify when a newly installed tool isn't found.Using Variables in Scripts
Accessing Variables
#!/bin/bash
# Access a variable
echo "Running in $APP_ENV environment"
# Use in path
CONFIG_PATH="/etc/myapp/${APP_ENV}.yaml"
echo "Loading config from $CONFIG_PATH"
# Default values with ${VAR:-default}
TIMEOUT=${TIMEOUT:-30}
# Uses TIMEOUT if set, otherwise defaults to 30Exporting Variables for Child Processes
#!/bin/bash
# Export makes variable available to child processes
export JAVA_OPTS="-Xmx512m"
# This subprocess sees JAVA_OPTS
./start-server.sh
# But parent shell doesn't see changes from child
./configure.sh # Sets API_KEY in its environment
# NOT visible to parent shellEnvironment Variables in Docker
Docker makes environment variables critical for configuration:
Passing Env Vars to Containers
# Set environment variable in container
docker run -e APP_ENV=production nginx
# Multiple variables
docker run -e APP_ENV=production -e DB_HOST=db.internal myapp
# Read from current environment (pass-through)
docker run --env-file .env myapp
# Inside container:
# $ echo $APP_ENV
# productionDocker Compose Environment Variables
services:
app:
image: myapp:latest
environment:
- APP_ENV=production
- DB_HOST=postgres
- DB_PASSWORD=${DB_PASSWORD} # From host .env file
ports:
- "8080:8080".env Files
# .env file (never commit to git!)
APP_ENV=production
DB_PASSWORD=super_secret
API_KEY=abc123xyz
# Load into environment
set -a
source .env
set +a
# Or use it with Docker Compose
docker-compose --env-file .env.prod upSecrets Management
Anti-Patterns to Avoid
# BAD: Hardcoded secrets in scripts
DB_PASSWORD="my_secret_password" # Committed to git!
./deploy.sh
# BAD: Secrets in configuration files in git
# config.yaml:
# password: "super_secret" # In git history forever!
# BAD: Secrets in Docker images
# RUN echo "password=secret" >> app.conf # In image layers!Better Approaches
# 1. Use environment variables from secure storage at runtime
export $(vault kv get -field=value secret/myapp/db_password)
./start-app.sh
# 2. Use Docker secrets (Swarm mode)
echo "my_secret" | docker secret create db_password -
docker service create --secret db_password myapp
# 3. Use Kubernetes Secrets
kubectl create secret generic db-creds \
--from-literal=password=my_secret
# Reference in pod spec as env vars or mountsReading Secrets at Runtime
# From HashiCorp Vault
vault write secret/myapp/db password="db_secret_123"
# Retrieve and use
DB_PASS=$(vault read -field=password secret/myapp/db)
export DB_PASSWORD=$DB_PASS
./app
# From AWS Secrets Manager
aws secretsmanager get-secret-value \
--secret-id prod/myapp/db \
--query SecretString \
--output textSystem-Wide Environment Variables
/etc/environment
# System-wide variables (not shell scripts — parsed by PAM)
$ cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
EDITOR=vim
# Add a variable here
echo 'JAVA_HOME="/usr/lib/jvm/default-java"' | sudo tee -a /etc/environment/etc/profile.d/
# Scripts in /etc/profile.d/ are sourced for all users at login
$ ls /etc/profile.d/
bash_completion.sh vim.sh Z99-java.sh
# Example: set Java environment
$ cat /etc/profile.d/java.sh
export JAVA_HOME=/usr/lib/jvm/java-17
export PATH=$JAVA_HOME/bin:$PATHDebugging Environment Variables
# See if a variable is set (even if empty)
$ echo ${VAR:-"not set"}
not set
$ export VAR=""
$ echo ${VAR:-"not set"}
(prints empty line, variable is set but empty)
# Check if variable is defined at all
$ if [ -z "${VAR+_}" ]; then echo "unset"; else echo "set"; fi
unset
# Print all variables containing a pattern
env | grep PATH
env | grep -i dockerQuick Reference
| Task | Command |
|---|---|
| List all env vars | printenv |
| Print specific var | echo $VAR_NAME |
| Set temp variable | export VAR=value |
| Set persistent var | Add to ~/.bashrc |
| Reload profile | source ~/.bashrc |
| Find command location | which cmd |
| Find all locations | which -a cmd |
| Add to PATH | export PATH="/new/bin:$PATH" |
| Unset variable | unset VAR_NAME |
| Read .env file | set -a && source .env && set +a |
| Check if set | echo ${VAR:-"default"} |
Practice Challenge
- Create an environment variable
DEPLOY_ENV=stagingand verify it withecho - Add it to your
~/.bashrcso it persists across sessions - Use
whichto find wherepython3ornodeis installed - Check your current PATH — is
/usr/local/binin it? Where? - Create a
.envfile withAPP_VERSION=1.0.0and load it into your current shell - What does
env | wc -lshow — how many environment variables are set?