Skip to main content
CodeOath
← All posts

Docker105 min total · 19 parts

Docker Fundamentals: Images, Containers, and Writing a Good Dockerfile

Part 15 of 19 · ~2 min

Environment-Specific Configuration

The image you push to Berrywell's server should be the exact same bytes that passed every test on staging — nothing about the artifact itself should be different between your laptop, staging, and a customer's box; only the settings handed to it should change. The mechanism for that is environment variables, handed to the container the moment it starts rather than fixed inside the image ahead of time:

docker run -e DATABASE_URL=postgres://prod-host/snapledger -e LOG_LEVEL=warn snapledger-web
services:
  web:
    image: snapledger-web:1.4.2
    env_file: .env.production   # loaded at container start, not at build time

Put a value like that into ENV in the Dockerfile instead, and every environment now needs its own separately built image to match — which undoes the entire point of building one image in the first place: there's no longer any way to be sure what's actually running on Berrywell's server is the exact thing that passed every test.

Priya finds this out for real, early on, taking a shortcut under deadline pressure. Rather than wiring up env_file properly for a one-off staging test, she adds one line straight to web's Dockerfile — ENV DATABASE_URL=postgres://staging-host/snapledger — builds it, and tags the result the way you'd tag anything meant to ship:

docker build -t snapledger-web:1.3.0 .
docker push snapledger-web:1.3.0

Nothing about that tag says "staging" anywhere. It's 1.3.0, the same version number the release was already going to use, and a week later that exact image — Postgres URL hardcoded and all — gets pulled and deployed to Berrywell's server during a rushed end-of-day release, because nobody checking the tag had any way to know it was different from any other 1.3.0 build. Production traffic points at the staging database for eleven minutes before someone notices expense rows aren't showing up where they should. Nothing was lost — staging and production run the same schema — but it's exactly the kind of incident that's structurally impossible once configuration lives outside the image entirely: an ENV baked into a Dockerfile is invisible in the tag, invisible in the registry, and only obvious once you're already debugging why something's pointed at the wrong database. docker inspect would have shown it in seconds, but nobody thinks to run docker inspect on an image that's supposedly identical to every other build with the same version number.

This is the same "promote one build artifact through every environment" idea covered in CI/CD Pipelines Explained. The image doesn't get to know which environment it's landing in; that decision stays outside it, made fresh every time a container actually starts.