Chapter 15Lesson 02~100 minutes

Driving Docker and Container CLIs

Container CLIs expose powerful lifecycle operations, but a robust wrapper must know whether the daemon is available, which image is intended, what state the container is in, and how replacement and cleanup are scoped.

IntermediateDevOps CLI integrationHands-on lab

Learning objectives

By the end of this lesson

  • Verify Docker CLI and daemon separately.
  • Use inspect rather than table parsing.
  • Make run configuration explicit.
  • Protect environment and credential data.
  • Recognize when a higher-level orchestrator is more appropriate.

1. Treat container CLIs as structured state machines

Docker commands create, inspect, start, stop, tag, push, and remove resources. Reliable shell automation should inspect current state and use deterministic names, labels, and image references.

Container lifecycle
flowchart TD
  I["image ref"] --> C["container create/run"]
  C --> H["health / status"]
  H --> L["logs / inspect"]
  L --> R["cleanup / replace"]

2. Verify the CLI and daemon separately

command -v docker >/dev/null 2>&1 || {
  printf 'docker CLI missing\n' >&2
  exit 69
}

docker info >/dev/null 2>&1 || {
  printf 'docker daemon unavailable or inaccessible\n' >&2
  exit 69
}

Finding the CLI executable does not prove the daemon is running or the current user has permission to access it.

3. Prefer immutable image identifiers for deployment

image='example/api@sha256:0123456789abcdef...'
docker pull "$image"

Tags such as latest are mutable references. For reproducible promotion, pin a digest or an otherwise immutable release identifier when your workflow supports it.

4. Use inspect formatting instead of parsing table output

status=$(
  docker inspect \
    --format '{{.State.Status}}' \
    my-container
)

printf 'status=%s\n' "$status"

Human-friendly docker ps columns are not a stable parsing interface.

5. Make run configuration explicit

docker run \
  --detach \
  --name api \
  --restart unless-stopped \
  --publish 8080:8080 \
  --env-file ./api.env \
  "$image"

Names, ports, restart policy, environment, volumes, networks, and image reference are part of the deployment contract.

6. Protect environment and secret values

Environment files are convenient but may contain credentials. Restrict permissions, keep them out of source control, and prefer your platform's secret facilities when appropriate.

Do not echo secret-bearing docker commands

Debug tracing with set -x can expose environment values or registry credentials.

7. Replace containers deliberately

if docker container inspect api >/dev/null 2>&1; then
  docker stop --time 20 api
  docker rm api
fi

docker run --detach --name api "$image"

For production systems, orchestrators often provide safer rolling replacement than stop/remove/run logic. This pattern is mainly appropriate for bounded single-host automation.

8. Capture diagnostics without losing exit status

if ! docker start api; then
  status=$?
  docker logs --tail 100 api >&2 || true
  exit "$status"
fi

When a lifecycle command fails, logs and inspect output are valuable context, but preserve the primary failure status first.

9. Cleanup commands can be destructive

docker image prune --filter 'until=168h'
Scope cleanup carefully

Prune commands can remove resources other workflows still need. Preview and label resources so cleanup operates on an intentional scope.

10. Use higher-level container tools when the topology grows

Once several containers, networks, volumes, health dependencies, and rollout order are involved, Docker Compose or an orchestrator is usually clearer than dozens of individual Bash-wrapped Docker commands.

11. Hands-on lab: container preflight and inspect helper

mkdir -p "$HOME/devops-academy/bash/chapter15/lesson02"
cd "$HOME/devops-academy/bash/chapter15/lesson02"

cat > docker-check.sh <<'EOF'
#!/usr/bin/env bash
set -u

name=${1:-}

command -v docker >/dev/null 2>&1 || {
  printf 'docker CLI missing\n' >&2
  exit 69
}

docker info >/dev/null 2>&1 || {
  printf 'docker daemon unavailable\n' >&2
  exit 69
}

if [[ -z $name ]]; then
  printf 'docker=ready\n'
  exit 0
fi

if docker container inspect "$name" >/dev/null 2>&1; then
  state=$(docker inspect --format '{{.State.Status}}' "$name")
  image=$(docker inspect --format '{{.Config.Image}}' "$name")
  printf 'container=%s state=%s image=%s\n' "$name" "$state" "$image"
else
  printf 'container=%s state=absent\n' "$name"
fi
EOF

chmod u+x docker-check.sh
./docker-check.sh

Verification checklist

12. Knowledge check

Question 1. Why is a Docker tag such as latest weaker for reproducibility?

Question 2. Why prefer docker inspect --format?

Question 3. Does the Docker CLI being installed prove containers can be managed?

Question 4. When should Bash stop managing individual container lifecycle commands?

13. Summary

Reliable Docker scripting separates CLI and daemon readiness, uses deterministic names and immutable image references where possible, reads state through inspect, protects secrets, preserves failure diagnostics, and avoids overly broad cleanup.

14. Further reading

  • Docker CLI reference — run, inspect, logs, image pull, container lifecycle.
  • Docker image digest and tagging documentation.
  • Docker Compose documentation.
  • Docker security guidance for credentials and daemon access.
Next lesson

kubectl and Kubernetes Automation

Continue Chapter 15 by applying safe Bash orchestration patterns to another core DevOps tool.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.