Chapter 23Lesson 02~120 minutes

Logs, Logging Drivers, Rotation, stdout/stderr Contracts, Events, stats, and Container Observability: Guided Hands-On Workflow and Core Operations

Run safe Docker observability labs with structured stdout/stderr, rotating local logs, bounded log queries, filtered events, stats, and daemon evidence.

Container observabilityLogging driversEvents & statsRotation & retentionEvidence-first

Learning objectives

  • Emit structured stdout and stderr from a disposable container and retrieve only bounded time windows.
  • Use a per-container rotating local driver without changing daemon configuration.
  • Capture filtered Engine events and a point-in-time stats sample, then correlate them with application logs.
  • Inspect the active logging driver and daemon logs safely without printing credentials or reading Docker-managed log files directly.
Lab safety. Everything uses exact disposable container names and per-container logging options. There is no daemon restart, no daemon.json edit, no Docker socket mount, no privileged mode, no broad prune, and no external credential. The synthetic “token” text used in one negative example is explicitly fake.

1. Preflight and lab identity

Record the daemon and image identity first. The examples use alpine:3.22; record the resolved repository digest because the human-readable tag is not the immutable evidence.

mkdir -p dkr23-evidence

docker context show | tee dkr23-evidence/context.txt
docker version | tee dkr23-evidence/docker-version.txt
docker info | tee dkr23-evidence/docker-info.txt
docker info --format 'default_logging_driver={{.LoggingDriver}}' | tee dkr23-evidence/default-driver.txt
docker compose version | tee dkr23-evidence/compose-version.txt 2>&1 || true
docker buildx version | tee dkr23-evidence/buildx-version.txt 2>&1 || true

docker pull alpine:3.22
docker image inspect alpine:3.22 --format '{{json .RepoDigests}}' | tee dkr23-evidence/image-digests.txt

2. Emit structured stdout and stderr

Create one short-lived producer with a stable label. JSON Lines are convenient for machines, but Docker does not require JSON. We emit separate stdout and stderr records and intentionally avoid credentials.

docker rm -f dkr23-streams 2>/dev/null || true

docker run --name dkr23-streams   --label academy=docker-ch23   --label component=stream-demo   alpine:3.22 sh -c '
    i=1
    while [ "$i" -le 5 ]; do
      printf "{\"level\":\"info\",\"seq\":%s,\"msg\":\"heartbeat\"}\n" "$i"
      if [ "$i" -eq 3 ]; then printf "{\"level\":\"warn\",\"seq\":3,\"msg\":\"synthetic warning\"}\n" >&2; fi
      sleep 1
      i=$((i+1))
    done
  '

docker inspect dkr23-streams --format 'id={{.Id}} image={{.Image}} exit={{.State.ExitCode}} log={{json .HostConfig.LogConfig}}'   | tee dkr23-evidence/streams-inspect.txt

3. Read only the log window you need

docker logs can add RFC3339Nano timestamps with --timestamps. --tail, --since, and --until keep an incident query bounded. Avoid dumping an entire high-volume container during diagnosis.

docker logs --timestamps --tail 20 dkr23-streams 2>&1   | tee dkr23-evidence/streams-logs.txt

docker logs --timestamps --since 10m dkr23-streams 2>&1   | tee dkr23-evidence/streams-last-10m.txt

4. Use a rotating driver per container

The mandatory lab uses local only for this disposable container. A small rotation threshold makes the policy observable without generating gigabytes of output. Inspect the configuration; do not browse Docker’s internal driver files.

docker rm -f dkr23-rotate 2>/dev/null || true

docker run -d --name dkr23-rotate   --label academy=docker-ch23   --log-driver local   --log-opt max-size=100k   --log-opt max-file=2   alpine:3.22 sh -c '
    i=1
    while [ "$i" -le 5000 ]; do
      printf "seq=%05d payload=abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz\n" "$i"
      i=$((i+1))
    done
    sleep 30
  '

sleep 2
docker inspect dkr23-rotate --format '{{json .HostConfig.LogConfig}}'   | tee dkr23-evidence/rotate-logconfig.json
docker logs --tail 20 --timestamps dkr23-rotate   | tee dkr23-evidence/rotate-tail.txt
Expected observation. The container declares local, max-size=100k, and max-file=2. Rotation limits driver retention; it does not change what the application attempted to emit. You should not expect the oldest generated records to remain indefinitely.

5. Capture a bounded Engine-event timeline

Create a long-running app, record a start timestamp, sample stats, then stop and restart it. After the actions, query only this label and time window. This avoids a noisy live terminal and produces an evidence file you can compare to logs.

docker rm -f dkr23-app 2>/dev/null || true
START_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)

docker run -d \
  --name dkr23-app \
  --label academy=docker-ch23 \
  --label component=observed-app \
  --log-driver local \
  --log-opt max-size=1m \
  --log-opt max-file=3   alpine:3.22 sh -c '
    trap "echo event=term_received ts=$(date -u +%Y-%m-%dT%H:%M:%SZ); exit 0" TERM
    i=0
    while :; do i=$((i+1)); echo "event=heartbeat seq=$i"; sleep 1; done
  '

sleep 3
docker stats --no-stream --format '{{json .}}' dkr23-app   | tee dkr23-evidence/app-stats.json
docker stop -t 5 dkr23-app
docker start dkr23-app
sleep 2
END_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)

docker events \
  --since "$START_UTC" \
  --until "$END_UTC" \
  --filter type=container \
  --filter label=academy=docker-ch23 \
  --format '{{json .}}'   | tee dkr23-evidence/app-events.jsonl

docker logs --timestamps --since "$START_UTC" dkr23-app 2>&1   | tee dkr23-evidence/app-logs.txt

6. Inspect the active driver and current state

Do not infer driver choice from the daemon default because a container may override it. Inspect the exact object you are diagnosing.

docker inspect dkr23-app \
  --format 'id={{.Id}} image={{.Image}} started={{.State.StartedAt}} status={{.State.Status}} restart_count={{.RestartCount}} log={{json .HostConfig.LogConfig}} labels={{json .Config.Labels}}'   | tee dkr23-evidence/app-inspect.txt

7. Daemon logs: read-only, platform-aware evidence

If you are on a systemd Linux host and have permission, capture a small recent daemon window. On Docker Desktop, use the documented Desktop log location instead. Failure to read daemon logs is a permissions/platform fact—not a reason to broaden privileges.

# Optional on Linux/systemd; read-only and bounded.
journalctl -u docker.service --since '10 minutes ago' --no-pager   | tail -n 200 > dkr23-evidence/daemon-recent.txt 2>&1 || true

8. Challenge: choose the failing layer

Suppose docker logs shows heartbeats until 12:00:10, docker events records die at 12:00:11, and a point-in-time stats sample at 12:00:09 shows memory near its limit. Which evidence should you inspect next?

Reasoned answer: first inspect .State.ExitCode/.State.OOMKilled and resource configuration, then cgroup/resource evidence if available. Do not start by changing the logging driver; the available evidence already points toward process/resource termination.

9. Exact cleanup

Remove only the named lab containers. Keep the evidence directory. Do not use a system-wide prune.

docker rm -f dkr23-streams dkr23-rotate dkr23-app 2>/dev/null || true
docker ps -a --filter label=academy=docker-ch23

Knowledge check

Why use a per-container local driver in the lab?

What does --timestamps change?

Why query events by label and time range?

If a container overrides the logging driver, does docker info show that override?

Why is direct access to Docker-managed local/json log files excluded?

Next lesson

Next: Logs, Logging Drivers, Rotation, stdout/stderr Contracts, Events, stats, and Container Observability: Configuration, Design Choices, and Tradeoffs

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

Version baseline, verified 2026-09-21.

Docker Engine 29.8.1 is the current Engine baseline used for compatibility notes. The daemon default logging driver remains json-file; Docker recommends local for general use because it rotates by default. The mandatory labs configure logging per container so they do not require editing daemon.json or restarting Docker. Always record docker version, docker info, context, the actual per-container HostConfig.LogConfig, and platform-specific daemon-log location instead of assuming defaults.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.