Production scripts never hardcode hostnames, passwords, or environment-specific values. Instead, they read from config files that are separate from the code, can be different per environment, and are managed independently. This page covers every config pattern you'll encounter in real bash projects.
1
.env files — the most common pattern
BASH
# .env file format — simple KEY=VALUE pairs
# /etc/myapp/production.env
DB_HOST=prod-db-01.internal
DB_PORT=3306
DB_NAME=myapp
DB_USER=app_user
DB_PASS=s3cur3p@ssw0rd
LOG_LEVEL=WARN
MAX_WORKERS=16
# ── Source .env file ──────────────────────────────────────
# Method 1: simple source (variables are exported)
set -a # auto-export all vars
source /etc/myapp/production.env
set +a # stop auto-export
# Method 2: safe source with validation
load_env() {
local env_file="${1}"
[[ -f "${env_file}" ]] || { echo "ERROR: ${env_file} not found" >&2; return 1; }
[[ -r "${env_file}" ]] || { echo "ERROR: ${env_file} not readable" >&2; return 1; }
# Only export valid KEY=VALUE lines — ignore comments, blank lines
while IFS= read -r line; do
[[ "${line}" =~ ^[[:space:]]*# ]] && continue # skip comments
[[ "${line}" =~ ^[[:space:]]*$ ]] && continue # skip blank lines
[[ "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || continue # skip invalid
export "${line?}"
done < "${env_file}"
}
load_env /etc/myapp/production.env
2
Read INI / key=value config files
BASH
# Config file: /etc/myapp/config.ini
# [database]
# host = prod-db-01
# port = 3306
# [server]
# workers = 8
# ── Read a single value by section and key ────────────────
get_ini_value() {
local file="${1}"
local section="${2}"
local key="${3}"
awk -F ' *= *' \
-v section="${section}" \
-v key="${key}" '
/^\[/ { in_section = ($0 == "[" section "]") }
in_section && $1 == key { print $2; exit }
' "${file}"
}
DB_HOST=$(get_ini_value /etc/myapp/config.ini database host)
DB_PORT=$(get_ini_value /etc/myapp/config.ini database port)
WORKERS=$(get_ini_value /etc/myapp/config.ini server workers)
echo "DB: ${DB_HOST}:${DB_PORT}, Workers: ${WORKERS}"
# ── Write / update a value in config ─────────────────────
set_config_value() {
local file="${1}"
local key="${2}"
local value="${3}"
if grep -q "^${key}=" "${file}"; then
sed -i "s|^${key}=.*|${key}=${value}|" "${file}"
else
echo "${key}=${value}" >> "${file}"
fi
}
set_config_value /etc/myapp/config.env LOG_LEVEL DEBUG
3
Config validation
BASH
#!/usr/bin/env bash
# validate_config.sh — Validate all required config values
validate_config() {
local -i errors=0
# Required: must be set and non-empty
for var in DB_HOST DB_USER DB_PASS APP_ENV; do
[[ -n "${!var:-}" ]] || {
echo "ERROR: ${var} is required" >&2
(( errors++ ))
}
done
# Numeric validation
[[ "${DB_PORT:-3306}" =~ ^[0-9]+$ ]] || {
echo "ERROR: DB_PORT must be numeric" >&2; (( errors++ ))
}
(( DB_PORT >= 1 && DB_PORT <= 65535 )) 2>/dev/null || {
echo "ERROR: DB_PORT must be 1-65535" >&2; (( errors++ ))
}
# Enum validation
case "${APP_ENV:-}" in
development|staging|production) ;;
*) echo "ERROR: APP_ENV must be development|staging|production" >&2
(( errors++ )) ;;
esac
# File path validation
[[ -d "${LOG_DIR:-/var/log}" ]] || {
echo "ERROR: LOG_DIR '${LOG_DIR}' does not exist" >&2; (( errors++ ))
}
if (( errors > 0 )); then
echo "Config validation failed with ${errors} error(s)" >&2
return 1
fi
echo "Config validation passed"
}
load_env "/etc/myapp/${APP_ENV:-development}.env"
validate_config || exit 1
vriddh@prod-01:~/scripts$APP_ENV=staging ./validate_config.sh
ERROR: DB_PASS is required
Config validation failed with 1 error(s)
vriddh@prod-01:~/scripts$DB_PASS=secret APP_ENV=staging ./validate_config.sh
Config validation passed
█
⚠ Never commit secrets — Config files with passwords must never be committed to git. Use
.gitignore to exclude *.env and config/*.env. Use a secrets manager (HashiCorp Vault, AWS Secrets Manager) or environment variables injected by your CI/CD system for production credentials.✔ Config management rules — Always separate config from code. Always validate required values at startup — fail fast with clear messages. Use environment-specific files (
production.env, staging.env). Never store production credentials in scripts or git. Always provide sensible defaults for optional values using ${VAR:-default}.