Pillar Guide · Knowledge Hub

Docker: Packaging Software So It Runs the Same Everywhere

A practical guide to containers — what problem they actually solve, the handful of concepts that matter, how to build images that are small and secure, and the mistakes that cause production incidents.

Docker Updated 2026-08-04 1116 words · about 5 min read

"It works on my machine" is the oldest complaint in software, and it is almost always true. The machine has a particular version of a language runtime, particular libraries installed years ago, particular environment variables nobody documented.

A container packages the application together with everything it needs to run into a single artefact. Move it anywhere with a container runtime and it behaves the same, because it brings its world with it.

That is the whole value proposition. Everything below is how to do it well.

Containers versus virtual machines#

The distinction that clarifies everything else:

A virtual machine emulates a whole computer, including its own operating system kernel. Heavy — gigabytes, and tens of seconds to start.

A container shares the host's kernel and isolates only the application and its dependencies. Light — often tens of megabytes, and starts in under a second.

That difference in weight is what makes containers practical for packaging every service individually, and for starting and stopping them constantly.

The trade-off: containers share a kernel, so isolation is weaker than a VM's. For most workloads that is fine. For running genuinely untrusted code, it is a real consideration.

The four concepts you need#

Image — the packaged application. Read-only, built once, versioned. Analogous to an installer.

Container — a running instance of an image. Ephemeral: it can be destroyed and recreated at any time, and any change made inside it is lost.

Dockerfile — the recipe describing how the image is built. This is code and belongs in version control.

Registry — where images are stored and shared.

The critical mental shift: containers are disposable. Anything you need to keep must live outside — in a database, in object storage, or on a mounted volume. Treating a container as a long-lived server you log into and modify defeats the point and produces state nobody can reproduce.

Building images well#

Start from a specific, minimal base. python:3.12-slim, not python:latest. latest means your build is not reproducible — the same Dockerfile produces different images on different days, which turns a working build into a mystery.

Use multi-stage builds. Compile in one stage with the full toolchain, copy only the artefact into a clean final stage. Images frequently drop from over a gigabyte to under a hundred megabytes, and the build tools are no longer sitting in production.

Order instructions from least to most frequently changing. Dependencies first, application code last. Layers are cached, and a change to code should not force reinstalling every dependency.

Do not run as root. Create a user and switch to it. This is one line and it substantially limits what a compromised container can do.

Never put secrets in an image. They persist in the layer history even if a later instruction deletes them. Pass secrets at runtime.

Add a .dockerignore. Otherwise you copy your .git directory, local environment files and node_modules into the image — larger, slower and often a credential leak.

One process per container. If you need three things running, that is three containers.

Configuration and data#

Configuration comes from the environment, not from files baked into the image. The same image must run in test and production with only settings differing — otherwise you did not test what you shipped.

Data lives in volumes or external services. Container filesystems vanish. This catches people with databases and uploaded files, both of which need somewhere durable.

Logs go to standard output. Let the platform collect them. Writing log files inside a container means losing them when it restarts.

Compose, and what comes after#

For local development and simple deployments, Compose describes a set of containers and their relationships in one file — application, database, cache, started together with one command. For a development environment this is close to ideal, and it removes the day-long setup ritual new joiners usually endure.

For production across multiple machines you eventually need orchestration — but not as early as people assume. A single server running Compose, with a tested backup, serves a great many real workloads perfectly well. See our Kubernetes guide for the honest test of when you have outgrown it.

Security worth doing#

  • Scan images for known vulnerabilities, and rebuild regularly — a six-month-old image contains six months of unpatched base-layer flaws even if your code never changed
  • Pin base image versions, then update deliberately
  • Run as a non-root user
  • Drop capabilities you do not need, and use a read-only root filesystem where possible
  • Keep secrets out of images and out of environment variables where the platform offers better
  • Prefer official or verified base images; an arbitrary image from a public registry is code you are choosing to run

Mistakes that cause incidents#

Using latest in production. You cannot tell what is running or roll back to what was.

Storing data inside the container. Discovered during the first restart.

Enormous images. Slow deployment, slow scaling, more vulnerabilities, higher storage cost.

Building differently per environment. Build once, promote the same artefact. Rebuilding per environment means production runs something no one tested.

Ignoring the base image after launch. Your dependencies age whether or not your code changes.

FAQ#

Do we need Docker to deploy software?#

No. Plenty of software deploys perfectly well without containers. Containers help most when you have multiple services, multiple environments, or a "works on my machine" problem — and they make onboarding dramatically faster.

Is Docker the same as Kubernetes?#

No. Docker packages and runs containers. Kubernetes orchestrates many containers across many machines. You can use Docker without Kubernetes; most organisations should start that way.

Are containers secure?#

Reasonably, with sensible configuration — non-root, minimal base, scanned images, dropped capabilities. They are not a security boundary as strong as a virtual machine, because the kernel is shared. For untrusted workloads, use stronger isolation.

How big should an image be?#

Smaller is better, but do not optimise obsessively. Multi-stage builds and a slim base get most applications comfortably under a few hundred megabytes, which is fine. Going further has diminishing returns.

Can we run databases in containers?#

In development, absolutely. In production it is possible but requires real care around storage, backups and upgrades — and a managed database service usually offers better durability for less effort. Do it when you have a specific reason.

What about Windows applications?#

Windows containers exist and work, with a larger footprint and more constraints than Linux containers. If your application can run on Linux, that path is better supported and cheaper.

How do we handle secrets?#

Never in the image, never in the Dockerfile. Inject at runtime from a secret store. Environment variables are acceptable but visible to anyone who can inspect the container — use your platform's secret mechanism where one exists.

What else is coming for Docker

Pillar Guide Ready

The definitive explainer — start here.

Tutorials Soon

Step-by-step, with working examples.

Best Practices Soon

What holds up in production, and what quietly doesn't.

Checklists Soon

Run through before you ship.

Diagrams Soon

The architecture, drawn.

Downloads Soon

Templates and starter files you can edit.

Videos Soon

Walkthroughs.

FAQs Soon

The questions people actually ask.