Skip to content
DevOps devops containers 6 min read

What are Containers?

A container is a lightweight, isolated box that holds your application together with everything it needs to run — the code, the runtime (the language engine, like Node.js or Python), system libraries, and settings. Because the container carries its own dependencies, it runs the exact same way on your laptop, on a colleague’s machine, and on a production server. Containers are the foundation of modern DevOps, and understanding them is the first step before you touch Docker, Kubernetes, or CI/CD pipelines.

The “works on my machine” problem

Almost every developer has hit this: the app runs perfectly on your laptop, but it crashes the moment it lands on the server. The usual cause is a difference in the environment — a different version of a library, a missing system package, a mismatched language runtime, or a config file that only exists on your machine.

Traditionally teams tried to fix this with long setup documents, manual server tweaks, or configuration management tools. That is slow, error-prone, and hard to reproduce.

Containers solve the problem at the root. Instead of describing how to set up the environment, you package the environment itself alongside the app. If the container works on your machine, it works everywhere, because the environment travels with the application. This property is called portability.

Containers make builds reproducible, but they do not magically fix bad assumptions. If your app depends on a service (like a database) that lives outside the container, that service still has to be present and reachable wherever you run it.

Images vs containers

These two words get mixed up constantly, so let us define them clearly.

  • An image is a read-only template — a frozen snapshot of a filesystem plus instructions for what to run. Think of it like a recipe or a class definition. Images are built once and stored in a registry (a server that hosts images, covered later in this section).
  • A container is a running instance of an image. Think of it like a cake baked from the recipe, or an object created from a class. You can start many containers from the same image, and each one runs in its own isolated space.
ConceptImageContainer
StateStatic, read-onlyRunning, has live state
AnalogyRecipe / classCake / object
How manyOne imageMany containers from it
Where it livesRegistry or local diskYour machine’s memory and CPU
Created bydocker builddocker run

When a container starts, the engine adds a thin writable layer on top of the read-only image. Anything the app writes (logs, temp files) goes into that layer and disappears when the container is removed — unless you attach a volume (persistent storage, covered later). This is why containers are described as ephemeral: treat them as disposable and rebuild them freely.

How isolation actually works

Containers feel like tiny separate machines, but they are not virtual machines. They share the host’s Linux kernel (the core of the operating system) and rely on two built-in Linux features to stay isolated and bounded.

Namespaces — what a container can see

A namespace is a Linux kernel feature that gives a process its own private view of part of the system. Each container gets its own namespaces, so it sees only its own:

  • PID namespace — its own process list (its main app is PID 1, and it cannot see the host’s processes).
  • Network namespace — its own network interfaces, IP address, and ports.
  • Mount namespace — its own filesystem layout.
  • UTS namespace — its own hostname.

The result: a process inside the container behaves as if it has the machine to itself, even though dozens of containers share the same host.

cgroups — what a container can use

cgroups (control groups) are the second kernel feature. While namespaces control what a container can see, cgroups control how many resources it can use — CPU time, memory (RAM), disk I/O, and network bandwidth. This stops one runaway container from starving everything else on the server.

On Ubuntu 22.04 and 24.04 LTS, cgroups v2 is the default. You can confirm the kernel supports both features without installing anything:

# Check that cgroups v2 is mounted
mount | grep cgroup2
# List the namespace types your kernel exposes
ls -l /proc/self/ns

Output:

cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

total 0
lrwxrwxrwx 1 root root 0 Jun 16 10:22 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Jun 16 10:22 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Jun 16 10:22 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Jun 16 10:22 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Jun 16 10:22 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Jun 16 10:22 uts -> 'uts:[4026531838]'

Docker and other container runtimes simply wire these kernel features together for you so you never have to configure namespaces and cgroups by hand.

When to use containers — and when not to

Containers are a great fit for a wide range of work, but they are not the answer to every problem.

Use containers when:

  • You want the same environment in development, testing, and production.
  • You are deploying web apps, APIs, background workers, or microservices.
  • You need fast, repeatable deployments and easy rollbacks.
  • You want to run several versions of a tool (e.g. two Postgres versions) side by side without conflicts.

Think twice when:

  • You need to run a different operating system kernel — Linux containers need a Linux kernel, so they cannot run native Windows or macOS software directly.
  • You require the strong, hardware-level isolation of a full virtual machine for untrusted, security-sensitive workloads (see Containers vs VMs).
  • The app is a simple one-off script with no dependencies — containerizing it may add more overhead than value.

A quick taste

You do not need to understand Docker yet to picture the workflow. Pulling and running a ready-made image takes one command:

docker run hello-world

Output:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Docker downloaded the image from a registry, created a container from it, ran it, and printed the message — all in seconds. The dedicated Docker docs in this section walk through installing Docker, building your own images, and running real apps step by step.

Best Practices

  • Treat containers as disposable: never edit a running container by hand — change the image and rebuild instead.
  • Keep one main process per container (one app or service), so it stays small, focused, and easy to scale.
  • Store anything that must survive (databases, uploads) in volumes, never inside the container’s writable layer.
  • Always pin image versions (e.g. postgres:16) rather than latest, so builds stay reproducible.
  • Set CPU and memory limits in production so one container cannot exhaust the whole host.
  • Learn the kernel concepts (namespaces and cgroups) at a high level — they explain almost every “why” you will hit later.
Last updated June 15, 2026
Was this helpful?