Docker

Docker - Advanced

Page 2 of 2

Production Logging and Observability

The medium tier's stdout/stderr convention is the foundation; production adds structure on top of it. Writing logs as structured JSON rather than free-form text makes them genuinely searchable and filterable once they're aggregated across dozens or hundreds of running containers - "show me every error from this service in the last hour" is a real query against structured fields, not a hopeful text search.

{"level":"error","service":"checkout","message":"payment gateway timeout","request_id":"a1b2c3","timestamp":"2026-08-24T10:15:00Z"}

Log aggregators (Fluentd, Vector, or a cloud provider's native logging) collect stdout/stderr from every container and ship it somewhere centrally searchable - which is the entire reason the stdout/stderr convention exists in the first place: it's the one interface every container, regardless of what it's running, already exposes for free.

Never write logs to a file inside the container's own filesystem in production - it's invisible to log aggregation, and it disappears the instant the container is replaced, which in a production environment can happen at any moment for reasons that have nothing to do with anything going wrong (a routine deploy, an autoscaling event).


Preparing Images for Orchestration

Almost every production container eventually runs under an orchestrator - Kubernetes, ECS, Nomad - and a few things that don't matter running a container by hand start to matter a lot once something else is managing its lifecycle.

Graceful shutdown: when an orchestrator wants to stop a container (during a rolling update, a scale-down, anything routine), it sends a SIGTERM signal and then waits a grace period before forcibly killing it with SIGKILL if it hasn't stopped on its own. An app that ignores SIGTERM entirely gets forcibly killed on every single routine deploy - which for anything mid-request means dropped connections and failed requests that had nothing to do with an actual failure, just bad timing against a deploy.

process.on('SIGTERM', () => {
  server.close(() => process.exit(0));   // finish in-flight requests, then exit cleanly
});

Readiness vs liveness: these answer genuinely different questions, and conflating them causes real production incidents. Liveness asks "is this process fundamentally broken and should be restarted" - a deadlock, a crash loop. Readiness asks "should this container currently be receiving traffic right now" - which can be legitimately "no" even for a perfectly healthy process, like during startup while it's still connecting to a database. Wiring a slow startup dependency into a liveness check instead of a readiness check causes an orchestrator to needlessly restart a container that was never actually broken, just still starting up - a surprisingly common, confusing production issue.


CI/CD Image Build Practices

A production build pipeline earns a few habits that a local build doesn't need to bother with.

Immutable tags per build: tag every image with something unique and traceable back to exactly what produced it - a commit SHA or a build number - never build-then-deploy using only a mutable tag like latest (the "Tags Are Not Versions" problem from the hard tier, now with a CI pipeline attached to it).

docker build -t my-app:$(git rev-parse --short HEAD) .

Build once, promote everywhere: build a single image, then push that exact same image (by digest, not by rebuilding) through staging and into production. Rebuilding separately for each environment risks the subtle, hard-to-catch case where "what passed staging" and "what actually shipped to production" are two different builds that merely look the same on paper.

Cache-aware CI: CI runners often start from a completely clean environment on every run, throwing away the layer cache that made local builds fast. Registries and CI providers generally support pulling a previous image specifically to seed the cache before building:

docker pull my-app:latest || true
docker build --cache-from my-app:latest -t my-app:new .

None of this is exotic - it's the same handful of habits (immutable references, one build promoted everywhere, deliberate cache reuse) applied consistently, which is usually the actual difference between a production Docker setup that stays boring and one that generates a recurring stream of confusing incidents.

    Welcome to OpsQuiz!

    Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.