Bash is still the glue that holds a lot of DevOps workflows together. Whether you’re deploying services, wiring health checks into CI, or cleaning up logs on a forgotten VM, a few solid scripting patterns go a very long way.
In this post, you’ll find copy‑pasteable Bash snippets for:
Safer script defaults
Parameterized deployments
Health checks and rollbacks
Log rotation and cleanup
Simple CPU/memory watchdogs
Everything is written with day‑to‑day DevOps work in mind—not contrived toy examples.
1. Bash foundations that prevent outages
Even experienced engineers skip basics that later cause flaky scripts and silent failures.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -e – exit on the first error instead of continuing in a bad state
set -u – fail if a variable is undefined
set -o pipefail – make pipelines fail if any command fails
Add a tiny logging helper:
log() { echo "[$(date +'%F %T')] $*"; }
die() { log "ERROR: $*"; exit 1; }
This alone makes every script more observable and safer to reuse across environments.
2. Parameters, flags, and environment‑aware scripts
Hard‑coding values is fine for demos, but production scripts must be configurable.
Use arguments with sensible defaults:
#!/usr/bin/env bash
set -euo pipefail
ENVIRONMENT="${1:-staging}"
log() { echo "[$(date +'%F %T')] [$ENVIRONMENT] $*"; }
log "Deploying to $ENVIRONMENT"
For more control, turn your script into a tiny CLI:
while getopts "e:v:h" opt; do
case "$opt" in
e) ENVIRONMENT="$OPTARG" ;;
v) VERSION="$OPTARG" ;;
h) echo "Usage: deploy.sh -e <env> -v <version>"; exit 0 ;;
*) exit 1 ;;
esac
done
Now teammates and CI pipelines can call the same script with explicit flags.
3. A realistic deployment script pattern
Here’s a trimmed‑down deployment flow you can adapt for your services.
#!/usr/bin/env bash
set -euo pipefail
ENVIRONMENT="${1:-staging}"
APP_DIR="/srv/myapp"
REPO_URL="git@github.com:org/myapp.git"
log() { echo "[$(date +'%F %T')] [$ENVIRONMENT] $*"; }
deploy() {
log "Updating code..."
if [[ ! -d "$APP_DIR/.git" ]]; then
git clone "$REPO_URL" "$APP_DIR"
fi
cd "$APP_DIR"
git fetch --all
git checkout main
git pull --ff-only
log "Installing dependencies..."
npm ci
log "Running tests..."
npm test
log "Building..."
npm run build
log "Restarting service..."
sudo systemctl restart myapp
log "Deployment complete."
}
deploy
This pattern is idempotent, easy to wire into CI/CD, and uses systemd for reliable service restarts.
4. Health checks and rollbacks in one script
Automation without safety is just a faster way to ship broken code. Add health checks:
health_check() {
local url="${1:-http://localhost/health}"
if curl -fsS "$url" > /dev/null; then
log "Health check passed for $url"
else
die "Health check FAILED for $url"
fi
}
Then define a simple rollback:
previous_version() {
git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo ""
}
rollback() {
local prev
prev="$(previous_version)"
[[ -z "$prev" ]] && die "No previous version found for rollback"
log "Rolling back to $prev"
git checkout "$prev"
npm run build
sudo systemctl restart myapp
}
Wire it together:
deploy
if ! health_check "https://myapp.example.com/health"; then
log "Health check failed; rolling back"
rollback
fi
You now have a single script that deploys, validates, and self‑heals.
5. Log rotation and cleanup that actually runs
Not every environment needs a full‑blown logging stack; Bash plus cron still works well.
Compress older logs:
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
DAYS_TO_KEEP=7
find "$LOG_DIR" -type f -name "*.log" -mtime +$DAYS_TO_KEEP -print0 \
| while IFS= read -r -d '' file; do
gzip "$file"
done
Remove stale archives:
find "$LOG_DIR" -type f -name "*.gz" -mtime +30 -delete
Schedule it:
30 1 * * * /usr/local/bin/log_cleanup.sh
That’s often enough to keep disks from filling up silently.
6. Lightweight monitoring and alert hooks
You can wrap traditional Linux tools in Bash and push alerts to Slack, email, or a webhook endpoint.
#!/usr/bin/env bash
set -euo pipefail
CPU_THRESHOLD=80
MEM_THRESHOLD=80
cpu_usage() {
mpstat 1 1 | awk '/Average/ && $12 ~ /[0-9.]+/ {print 100-$12}'
}
mem_usage() {
free | awk '/Mem:/ {printf("%.0f", $3/$2 * 100)}'
}
CPU=$(cpu_usage)
MEM=$(mem_usage)
if (( CPU > CPU_THRESHOLD || MEM > MEM_THRESHOLD )); then
echo "High usage detected: CPU=${CPU}% MEM=${MEM}%"
# TODO: send to Slack / email / webhook here
fi
This is a great complement to Prometheus, Grafana, or hosted solutions.
7. Safer configuration changes
Always pair config edits with backups.
CONFIG="/etc/myapp/config.yaml"
BACKUP="/etc/myapp/config.yaml.$(date +'%F-%H%M%S').bak"
cp "$CONFIG" "$BACKUP"
sed -i 's/feature_x: false/feature_x: true/' "$CONFIG"
systemctl restart myapp
This pattern makes it trivial to revert a bad change during an incident.
If you adapt any of these snippets into your own tooling, drop a comment with your variations—other engineers will benefit from seeing real‑world tweaks. Also, tell which part you’d like to see expanded: deployments, monitoring, or incident tooling.
Top comments (0)