Guide Bash-Scripting Advanced

Bash + Docker

Scripting Docker operations from Bash — container entrypoints, health-check waits, cleanup automation, and building images in CI.

3 min read

Writing a Proper Docker Entrypoint Script

#!/usr/bin/env bash
set -euo pipefail

# Wait for a dependency before starting the main process
until nc -z "$DB_HOST" "$DB_PORT"; do
    echo "Waiting for database at $DB_HOST:$DB_PORT..."
    sleep 1
done
echo "Database is available"

# exec replaces THIS shell process with the app, so signals (SIGTERM) reach it directly
exec "$@"
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server.js"]

exec "$@" is critical: without it, the app runs as a child of the bash script, and the script (PID 1 in the container) doesn’t forward SIGTERM to it — leading to slow, forced (SIGKILL) shutdowns instead of graceful ones.

Cleaning Up Docker Resources

#!/usr/bin/env bash
set -euo pipefail

echo "Removing stopped containers..."
docker container prune -f

echo "Removing dangling images..."
docker image prune -f

echo "Removing unused volumes..."
docker volume prune -f

echo "Removing images older than 30 days not used by a running container..."
docker images --format '{{.ID}} {{.CreatedAt}}' | while read -r id created; do
    AGE_DAYS=$(( ($(date +%s) - $(date -d "$created" +%s)) / 86400 ))
    if [ "$AGE_DAYS" -gt 30 ]; then
        docker rmi "$id" 2>/dev/null || true
    fi
done

Waiting for a Container to Be Healthy

CONTAINER="myapp"
MAX_WAIT=60
ELAPSED=0

until [ "$(docker inspect -f '{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null)" = "healthy" ]; do
    if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
        echo "Container did not become healthy in time" >&2
        docker logs --tail 50 "$CONTAINER"
        exit 1
    fi
    sleep 2
    ((ELAPSED += 2))
done
echo "$CONTAINER is healthy"

Scripting Builds and Pushes

#!/usr/bin/env bash
set -euo pipefail

IMAGE="myrepo/myapp"
TAG="${1:-$(git rev-parse --short HEAD)}"

docker build -t "$IMAGE:$TAG" -t "$IMAGE:latest" .
docker push "$IMAGE:$TAG"
docker push "$IMAGE:latest"
echo "Pushed $IMAGE:$TAG"

Extracting Info From Running Containers

docker inspect --format '{{.State.Pid}}' myapp        # host PID of the container's main process
docker inspect --format '{{.NetworkSettings.IPAddress}}' myapp
docker ps --filter "status=exited" --format "{{.Names}}: {{.Status}}"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

Running a Command Inside a Container From a Script

docker exec myapp printenv NODE_ENV
docker exec -it myapp /bin/sh -c "cat /app/config.json"

RESULT=$(docker exec myapp curl -sf http://localhost:8080/health)
echo "Health check result: $RESULT"

A Deploy Script Pattern: Rolling Container Restart

#!/usr/bin/env bash
set -euo pipefail

IMAGE="myrepo/myapp:latest"

echo "Pulling latest image..."
docker pull "$IMAGE"

echo "Starting new container alongside the old one..."
docker run -d --name myapp-new -p 8081:8080 "$IMAGE"

until curl -sf http://localhost:8081/health > /dev/null; do
    echo "Waiting for new container to be healthy..."
    sleep 2
done

echo "New container healthy — switching traffic and removing old one"
docker stop myapp-old 2>/dev/null || true
docker rm myapp-old 2>/dev/null || true
docker rename myapp myapp-old 2>/dev/null || true
docker rename myapp-new myapp

Production Considerations

  • Always exec "$@" at the end of an entrypoint script — otherwise the container’s main process never receives SIGTERM correctly, and Kubernetes/Docker will forcibly SIGKILL it after the grace period on every shutdown.
  • Prefer Docker’s built-in HEALTHCHECK + docker inspect .State.Health.Status over hand-rolled curl polling where possible — it’s visible to orchestrators too.
  • Automate image/volume cleanup on build/CI hosts — unused Docker images and volumes are a common, silent source of disk pressure on long-running CI runners.

Quick Interview Answer

“Bash and Docker meet mainly in entrypoint scripts and CI/deployment automation. The critical entrypoint pattern is ending with exec \"$@\" so the containerized app becomes PID 1 directly and receives SIGTERM correctly on shutdown — without it, the wrapping shell absorbs the signal and the app gets hard-killed instead. Beyond that, scripts commonly wait for dependencies (nc -z), poll container health (docker inspect .State.Health.Status), and automate build/push/cleanup in CI pipelines.”

Common Mistakes

  • Forgetting exec in an entrypoint script, causing slow/forced container shutdowns instead of graceful ones.
  • Hand-rolling health-check polling instead of using Docker’s native HEALTHCHECK directive where the orchestrator can act on it too.
  • Never running image/volume/container pruning, letting disk usage grow unbounded on build hosts over time.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form