Bash and Docker in DevOps: Managing Containers Efficiently
Comprehensive guide to combining Bash and Docker in DevOps: wrapper scripts, container management, image cleanup, healthchecks, deploying stacks with docker compose, and auto-restarting crashed containers.
In the previous post, we explored automating SSH to manage multiple servers in parallel. Now, we step into the container world—where Bash serves as a powerful bridge between you and the Docker daemon, enabling container management more flexibly than any UI.
The Docker CLI is already powerful, but when you need to run batches of containers, check health status periodically, clean up old images, or deploy a complex stack—manually typing commands one by one is no longer practical. Bash lets you write wrapper scripts to automate the entire container lifecycle: from build and deploy, to healthcheck, restart on crash, and resource cleanup.
This article covers techniques for combining Bash and Docker, from basic commands to a complete container management script.
Core Docker Commands in Bash
Listing and Filtering Containers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# List all running containersdocker ps
# List all containers including stopped onesdocker ps -a
# Output container IDs only (script-friendly)docker ps -q
# Filter by namedocker ps --filter "name=nginx"# Filter by statusdocker ps --filter "status=running"docker ps --filter "status=exited"# Combine filters with custom formatdocker ps --format "{{.ID}} {{.Names}} {{.Status}} {{.Ports}}"
Checking Container State
1
2
3
4
5
6
7
8
# Check if container is running (returns 0 if true)docker inspect --format='{{.State.Running}}' myapp
# Get detailed statusdocker inspect --format='{{.State.Status}}' myapp
# Check health status (if healthcheck is configured)docker inspect --format='{{.State.Health.Status}}' myapp
Managing Containers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Start a stopped containerdocker start myapp
# Stop a containerdocker stop myapp
# Restart a containerdocker restart myapp
# Remove a stopped containerdocker rm myapp
# Force remove (even if running)docker rm -f myapp
Docker Compose
1
2
3
4
5
6
7
8
9
10
11
# Start a stackdocker compose up -d
# Stop a stackdocker compose down
# View logsdocker compose logs -f
# Restart a specific servicedocker compose restart nginx
Bash Wrapper Scripts for Docker
Utility Functions
Before writing complex scripts, create reusable utility functions:
#!/usr/bin/env bash
# Check if Docker daemon is runningdocker_ready(){ docker info >/dev/null 2>&1}# Get container ID by name (empty if not found)container_id(){ docker ps -q --filter "name=$1"}# Check if container is runningcontainer_running(){[[ -n "$(container_id "$1")"]]}# Check if container exists (even if stopped)container_exists(){ docker ps -a -q --filter "name=$1"| grep -q .
}# Get container logs (last N lines)container_logs(){localname="${1:?Container name required}"locallines="${2:-50}" docker logs --tail "$lines""$name" 2>&1}
#!/usr/bin/env bash
# HTTP endpoint healthcheck for containerscheck_http(){localcontainer_name="${1:?Container name required}"localport="${2:-80}"localpath="${3:-/}"localexpected_status="${4:-200}"local ip
ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'"$container_name" 2>/dev/null)if[[ -z "$ip"]];thenecho"[ERROR] Cannot get IP for container $container_name"return1filocal status
status=$(curl -s -o /dev/null -w "%{http_code}""http://${ip}:${port}${path}" 2>/dev/null ||echo"000")if[["$status"=="$expected_status"]];thenecho"[OK] $container_name HTTP check passed (status: $status)"return0elseecho"[WARN] $container_name HTTP check failed (status: $status, expected: $expected_status)"return1fi}# Check multiple containerscheck_http "nginx"80"/""200"check_http "api"8080"/health""200"
Deploying Stacks with Bash
Deploying a Docker Compose stack typically requires: pulling new images, stopping old services, restarting, and checking status. Bash helps you orchestrate the entire process:
#!/usr/bin/env bash
# ==============================================================================# Script : docker-watchdog.sh# Purpose: Monitor containers and auto-restart on crash# Schedule via cron: */2 * * * * /opt/scripts/docker-watchdog.sh# ==============================================================================set -euo pipefail
IFS=$'\n\t'readonlyWATCHED_CONTAINERS="${WATCHED_CONTAINERS:-nginx,api,worker}"readonlyMAX_RESTART="${MAX_RESTART:-3}"readonlyRESTART_WINDOW="${RESTART_WINDOW:-300}"readonlyLOG_FILE="/var/log/docker-watchdog.log"readonlySTATE_DIR="/var/run/docker-watchdog"mkdir -p "$STATE_DIR"log(){echo"[$(date '+%Y-%m-%d %H:%M:%S')] $*"| tee -a "$LOG_FILE" 2>/dev/null
}# Count restarts within the time windowrestart_count(){localcontainer="$1"localstate_file="${STATE_DIR}/${container}.restarts"localcount=0if[[ -f "$state_file"]];thenwhileIFS=read -r timestamp;doif[[$(date +%s) -lt $((timestamp + RESTART_WINDOW))]];thencount=$((count +1))fidone < "$state_file"# Clean old timestamps awk -v cutoff=$(($(date +%s)- RESTART_WINDOW))'$1 >= cutoff'"$state_file" > "${state_file}.tmp" mv "${state_file}.tmp""$state_file"fiecho"$count"}record_restart(){localcontainer="$1"echo"$(date +%s)" >> "${STATE_DIR}/${container}.restarts"}# Check if container is runningis_running(){ docker ps --format '{{.Names}}'| grep -q "^$1$"}# Check if container exists (even if stopped)container_exists(){ docker ps -a --format '{{.Names}}'| grep -q "^$1$"}restart_container(){localcontainer="$1"local count
count=$(restart_count "$container")if[["$count" -ge "$MAX_RESTART"]];then log "[CRIT] $container restarted $count times in ${RESTART_WINDOW}s — manual intervention required"return1fi log "[WARN] $container is not running — restarting..." docker restart "$container" 2>/dev/null
record_restart "$container" sleep 5if is_running "$container";then log "[INFO] $container restarted successfully"else log "[ERROR] $container failed to restart"return1fi}check_container(){localcontainer="$1"if ! container_exists "$container";then log "[WARN] $container does not exist — skipping"return0fiif is_running "$container";then# Check health status if availablelocal health
health=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}'"$container" 2>/dev/null)if[["$health"=="unhealthy"]];then log "[WARN] $container is unhealthy" restart_container "$container"fielse restart_container "$container"fi}main(){IFS=','read -ra CONTAINERS <<<"$WATCHED_CONTAINERS"for container in "${CONTAINERS[@]}";docontainer=$(echo"$container"| xargs) check_container "$container"done}main "$@"
Cron Configuration
1
2
3
# Run every 2 minutescrontab -e
*/2 * * * * /opt/scripts/docker-watchdog.sh >> /var/log/docker-watchdog-cron.log 2>&1
Sample Output
1
2
3
4
[2026-08-31 10:00:01] [INFO] nginx is healthy
[2026-08-31 10:00:01] [WARN] api is not running — restarting...
[2026-08-31 10:00:06] [INFO] api restarted successfully
[2026-08-31 10:00:06] [INFO] worker is healthy
Deployment Notes & Best Practices
Docker socket permission: Bash scripts need access to the Docker daemon via the /var/run/docker.sock socket. Ensure the user running the script belongs to the docker group or has sudo privileges.
BatchMode SSH: When running Docker scripts via SSH (from the previous post), use ssh -o BatchMode=yes to avoid host key confirmation prompts.
Container naming convention: Always use --name when starting containers for easier management. Avoid using generated IDs as they are hard to remember and unstable.
Resource cleanup: The watchdog script only restarts containers—it does not auto-delete. Combine with periodic docker system prune runs to free up disk space.
Logging aggregation: In production environments, container logs should be shipped to a centralized logging system (ELK, Loki) rather than only writing to local files.
Conclusion
Combining Bash and Docker creates a powerful, flexible, and lightweight container management tool that surpasses any complex orchestration framework. From listing containers, checking health status, cleaning up resources, to writing a watchdog script that auto-restarts on crash—all within your grasp with just Bash and the Docker CLI.
In the next post, we will explore monitoring with Bash: collecting system metrics, sending reports via email/webhook, and building daily report dashboards.