G
GuideDevOps
Lesson 8 of 17

Environment Variables

Part of the Linux Fundamentals tutorial series.

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=less

Viewing 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=admin

echo — 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/bash

env — 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 command

Setting 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 terminal

Persistent 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
production

Common Profile Files

FileScopeWhen It Runs
/etc/environmentSystem-wideAll users at login
/etc/profileSystem-wideAll users at login
~/.bashrcUserEach bash session
~/.bash_profileUserLogin shells only
~/.profileUserLogin 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 ~/.bashrc

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 30

Exporting 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 shell

Environment 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
# production

Docker 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 up

Secrets 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 mounts

Reading 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 text

System-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:$PATH

Debugging 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 docker

Quick Reference

TaskCommand
List all env varsprintenv
Print specific varecho $VAR_NAME
Set temp variableexport VAR=value
Set persistent varAdd to ~/.bashrc
Reload profilesource ~/.bashrc
Find command locationwhich cmd
Find all locationswhich -a cmd
Add to PATHexport PATH="/new/bin:$PATH"
Unset variableunset VAR_NAME
Read .env fileset -a && source .env && set +a
Check if setecho ${VAR:-"default"}

Practice Challenge

  1. Create an environment variable DEPLOY_ENV=staging and verify it with echo
  2. Add it to your ~/.bashrc so it persists across sessions
  3. Use which to find where python3 or node is installed
  4. Check your current PATH — is /usr/local/bin in it? Where?
  5. Create a .env file with APP_VERSION=1.0.0 and load it into your current shell
  6. What does env | wc -l show — how many environment variables are set?