DevOps · Mid-level

Containers and Docker: why 'it works on my machine' is over

"It works on my machine" was for years the phrase that ended discussions without solving any problem. Containers put an end to it. By packaging the application together with everything it needs to run, the container guarantees the environment is the same on your machine, in CI and in production. Docker popularized this idea and it became required knowledge for any mid-level DevOps engineer.

Container vs virtual machine

The classic confusion is thinking a container is a lightweight VM. The difference is in what each one virtualizes. A VM emulates an entire computer, with its own full operating system. A container shares the host's kernel and isolates only what the application needs — which is why it's much lighter and faster.

AspectContainerVirtual machine
IsolatesProcess and dependenciesFull OS
KernelShared with the hostIts own
SizeMBsGBs
Start timeSecondsMinutes
DensityHundreds per hostA few dozen

Image vs container

This distinction comes up in interviews all the time. The image is the immutable mold, the packaged and versioned template. The container is a running instance of that image. A useful analogy: the image is the class, the container is the object. From a single image you can start as many containers as you like.

The Dockerfile

The Dockerfile is the recipe that describes, step by step, how to build the image. Each instruction is declarative and the result is reproducible:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Layers and build cache

Each Dockerfile instruction generates a layer, and Docker reuses the layers that haven't changed (cache). That's why, in the example above, we copy package.json and run npm ci before copying the rest of the code: as long as the dependencies don't change, that expensive layer stays cached and the build is much faster.

Order your Dockerfile from what changes least to what changes most. Stable things (dependencies) go on top, volatile code at the bottom — that way the cache works in your favor.

Registry: where images live

Once built, the image is pushed to a registry — Docker Hub, GitHub Container Registry, ECR, etc. It's the equivalent of a Git repository, but for images. The CI/CD pipeline builds the image, pushes it to the registry and the environments pull it from there.

docker build -t minha-app:1.2 .
docker push registry.exemplo.com/minha-app:1.2
docker run -p 3000:3000 minha-app:1.2

A reproducible environment from dev to production

The central gain: the same image you run on your laptop is the one that runs in production. Runtime versions, system libraries and configuration travel together. This eliminates the entire category of "it worked over there" bugs. And it's this portability that makes it possible to orchestrate containers at scale with Kubernetes.

Conclusion

Containers solved the reproducibility problem by packaging application and environment together, lightweight and portable. Understanding the difference between image and container, how layer caching works and the role of the registry is the minimum package a mid-level DevOps engineer needs to master — and the foundation on which orchestrators like Kubernetes are built.

Read also

Back to track