Skip to content

Git, Linux & DevOps

Docker and Containers

A container bundles an application together with its dependencies, libraries and configuration into a single image that runs consistently on any machine with a container runtime, regardless of what else is installed there. Docker is the tool that made this mainstream: it defines a simple format for building images (a Dockerfile), a way to run them as isolated processes (containers), and a registry system for sharing images. Containers are lighter than full virtual machines because they share the host's operating system kernel rather than each running their own.

Why it matters

It solves 'works on my machine'
A container packages the exact runtime, libraries and OS-level dependencies an app needs, so the same image that passed tests on a laptop runs identically in CI and in production.
It's the standard unit of deployment on most modern infrastructure
Cloud platforms, CI pipelines and orchestrators like Kubernetes are all built around running containers, so understanding them is close to a prerequisite for deploying anything at scale.
It makes environments reproducible and disposable
A container can be destroyed and recreated from its image in seconds, which changes how teams think about debugging (recreate rather than patch) and scaling (start more copies rather than resize one server).
It isolates dependencies between applications
Two services needing different, conflicting versions of the same library can run side by side on one host, each in its own container, without interfering.

Images vs containers

An image is a read-only template, built once from a Dockerfile, containing an application and everything it needs to run. A container is a running (or stopped) instance of that image, with its own writable layer added on top for anything it creates or changes while running. Many containers can be started from the same image, each isolated from the others.

Writing a Dockerfile

A Dockerfile is a short script that describes how to build an image, one instruction per line: FROM picks a base image, COPY brings in files, RUN executes a command during the build, and CMD specifies what runs when a container starts. Each instruction creates a cached layer, and Docker only rebuilds the layers after the first one that changed - which is why dependency installation is usually placed before copying the rest of the source code, so that changing application code doesn't force dependencies to reinstall on every build.

Shell
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Containers are not full virtual machines

A virtual machine virtualizes hardware and boots its own complete operating system kernel; a container is an isolated set of processes that shares the host machine's kernel, using Linux namespaces and cgroups to keep processes, network interfaces and resource limits separate. That's why containers start in a fraction of a second where a VM takes longer to boot, but it also means a container's isolation boundary is weaker than a VM's - it shares more with the host by construction.

Volumes and networking

A container's writable layer is deleted along with the container, so anything that needs to persist - a database's files, uploaded content - has to be written to a mounted volume instead of the container's own filesystem. Containers on the same network (as with Docker Compose or Kubernetes) can typically reach each other by service name, without needing to know each other's actual network addresses.

Mistakes people make here

Baking secrets into an image
anything copied or run into an image layer stays in the image's history and can be extracted even if a later layer deletes the file; secrets belong in environment variables or a secrets manager passed in at runtime, not in the Dockerfile.
Using :latest as the tag in production
it silently points to whatever the newest build is, so the same deployment command can pull a different image tomorrow than it did today, making rollbacks and reproducibility a guessing game; pin an explicit version or digest instead.
Expecting data written inside a container to survive
a container's writable layer is deleted with the container; anything that needs to persist has to be in a mounted volume, not just written to the container's filesystem.
Treating a container as a lightweight VM you SSH into and hand-configure
that throws away the main benefit - a container should be rebuildable from its Dockerfile at any time; manual changes made inside a running container vanish and can't be reproduced.
Copying the whole project before installing dependencies
putting COPY . . before the dependency install step means any source code change invalidates the cached dependency-install layer, so every build reinstalls everything from scratch instead of reusing the cache.

Strengths and trade-offs

Where it is strong

  • Starts in roughly the time a process takes to start, not the minutes a full VM boot takes, because there's no separate OS to boot.
  • An image is a precise, versioned artifact - the same image that passed tests is the one deployed, removing a whole class of environment drift.
  • Much higher density than VMs: many containers can share one host's kernel, so a host runs far more containers than it could full VMs.
  • A large ecosystem of pre-built images means most common software doesn't need to be installed and configured from scratch.

The trade-offs

  • Weaker isolation than a virtual machine: containers share the host kernel, so a kernel-level vulnerability can affect every container on that host in a way a hypervisor boundary would contain.
  • Persistent state needs deliberate volume management; it doesn't come free the way it does on a normal server.
  • Debugging a multi-container application has its own learning curve on top of debugging the application itself.
  • Image sizes and layer caching need attention, or builds get slow and images get large without anyone noticing.

Who needs this

Anyone deploying a backend service, running CI pipelines, or working with Kubernetes needs working knowledge of containers. Purely frontend or mobile-only work can usually skip it, though even those roles often run a local database or tool in a container.

Questions about docker and containers

Is a container a lightweight virtual machine?
Not really, even though it's often described that way. A VM virtualizes hardware and runs its own full operating system kernel; a container is an isolated set of processes that shares the host machine's kernel, isolated using Linux namespaces and cgroups. That's why containers start in a fraction of a second and VMs take longer to boot.
What's the difference between an image and a container?
An image is the built, read-only template - the result of running a Dockerfile. A container is a running (or stopped) instance of an image, with its own writable layer added on top. You can start many containers from the same image, the same way many files can be copied from one template.
Do I need Kubernetes to use Docker?
No. Docker, and tools like Docker Compose, is enough to build and run containers, including multi-container applications on one machine. Kubernetes is an orchestrator for running containers reliably across many machines at once - it matters once you have enough containers or enough scale that manual placement and restarts become impractical.
Why do people say containers aren't secure by default?
Because a container's default isolation is real but not absolute - a process that breaks out of a container's boundary, or a root process running unnecessarily inside one, has a bigger blast radius than the container model suggests at first glance. Running containers as a non-root user, keeping base images patched, and not disabling default security options are the usual mitigations.

The primary source

Related concepts

← All concept guides