The Docker Engine
A friendly, step-by-step tour of the software that builds, ships and runs containers: what is inside it, how it works, how to keep it safe and healthy, and how real companies use it every day.
Picture this. You finish building an app on your laptop, and it runs perfectly. You send it to a teammate, or to a server in the cloud, and it crashes. A library has a different version. A setting is missing. The operating system is not the same. Developers have joked about this for decades: “But it works on my machine!”
The Docker Engine was built to end that joke. It lets you pack an app together with everything it needs into a sealed, portable box called a container, and then run that box the same way on any computer that has the engine installed. In this guide you will learn what the Docker Engine is, what lives inside it, how it works step by step, how to keep it safe, and how companies such as Netflix and Amazon use the same ideas at huge scale. We explain every new word the first time it appears, so you can read from top to bottom without any earlier knowledge.
1What Is the Docker Engine?
The engine is the working heart of Docker. Everything else, from the laptop app to the cloud services, sits on top of it.
The Docker Engine is the software that creates and runs containers on a computer. A container is an app plus all the files, libraries and settings it needs, wrapped up so that it runs in its own private space. The app inside believes it has the whole computer to itself, even though many containers may be sharing one machine.
The engine is a client-server program made of three parts. The first is a long-running background program called the daemon, whose command name is dockerd. A daemon is simply a program that keeps running quietly in the background and waits for work. The second is an API, which is a set of agreed messages that other programs can send to the daemon to ask for things. The third is the command-line client, the docker command you type in a terminal. When you type a command, the client turns it into an API message and sends it to the daemon, and the daemon does the real work.
Think of the steel shipping containers stacked on cargo ships. Before they existed, workers loaded sacks, barrels and crates one by one, and every port did it differently. Once everyone agreed on one standard box size, any crane, truck, train or ship could move any box without caring what was inside. Docker did the same thing for software. The container is the standard box. The Docker Engine is the crane that knows how to lift it, place it and open it.
The problem it was created to solve
Before containers, teams usually installed apps directly on servers. Two apps on the same server might need different versions of the same library, and they would fight each other. Moving an app from a developer laptop to a test server to a production server meant repeating a long list of setup steps, and someone always forgot one. Virtual machines helped, but they were heavy and slow to start. Containers gave teams a lighter, faster way to get the same repeatable result.
A short history you can remember
Docker appears
A company then called dotCloud shows Docker at a Python conference. It builds on Linux features that already existed but makes them easy for everyday developers to use.
Its own engine and version 1.0
Docker replaces its early dependency on LXC, an older container tool, with its own Go library, and reaches its first stable release.
An open standard is born
The Open Container Initiative is formed so that the container format is not owned by one company. Docker donates its low-level runtime code, which becomes runc.
The engine is split into parts
The engine is reorganised around containerd and runc, and a built-in clustering feature called swarm mode arrives.
Moby and containerd go community-owned
The Moby project is announced as the open-source home of the engine’s building blocks, and containerd moves to the Cloud Native Computing Foundation.
Kubernetes stops talking to Docker directly
Kubernetes removes its old Docker adapter and uses containerd or similar runtimes. Images built with Docker keep working because they follow the open standard.
A modern builder becomes the default
BuildKit becomes the default image builder in the engine, making builds faster and smarter.
Docker Engine is the open-source runtime described in this guide. Docker Desktop is a desktop application for Mac, Windows and Linux that bundles the engine with a small hidden Linux virtual machine and extra tools. Docker Inc. is the company that steers the project and sells the desktop app and other services.
Why the ecosystem around it grew so quickly
Part of the reason Docker spread so fast is that it launched alongside Docker Hub, a public registry where anyone could publish a ready-made image for free. Instead of writing installation steps for a database or a web server from scratch, a developer could simply download an image someone else had already built, tested and shared. Within a few years, official images existed for almost every popular piece of software, and companies began publishing their own images for internal reuse. This network effect, an easy engine paired with a huge shared library of images, is a large part of why containers moved from a niche developer tool to something used by nearly every technology company within about a decade of that first public demonstration.
2Containers vs Virtual Machines
To understand why containers are popular, compare them with the older idea they often replace.
A virtual machine, or VM, is a whole pretend computer running inside a real one. A special layer of software called a hypervisor carves a physical server into several virtual servers. Each VM carries its own full operating system, its own memory and its own virtual hardware. That gives strong separation, but each VM may need several gigabytes of disk space and can take from tens of seconds to a few minutes to boot.
A container skips the pretend computer. All containers on a host share the kernel of the host’s operating system. The kernel is the core part of an operating system that talks to the hardware and decides which program gets the processor, memory and disk. Because there is no second operating system to boot, a container is usually just a few megabytes to a few hundred megabytes and can start in well under a second to a few seconds. A tiny base image such as Alpine Linux is only around five megabytes.
A virtual machine is like giving every family their own separate house, complete with its own foundation, pipes and wiring. A container is like an apartment in one big building. Every apartment has its own locked door, its own rooms and its own furniture, but they all share the same foundation, the same water supply and the same electrical system. Apartments are cheaper and quicker to set up. Houses give stronger walls between neighbours.
| Question | Virtual machine | Container |
|---|---|---|
| What is inside? | A full operating system plus your app | Your app plus its libraries only |
| Typical size | Gigabytes | Megabytes to a few hundred megabytes |
| Start-up time | Tens of seconds to minutes | Under a second to a few seconds |
| Kernel | Each VM has its own | Shared with the host |
| Isolation strength | Very strong | Good, but weaker because the kernel is shared |
| How many fit on one server | Tens | Hundreds or more |
| Best for | Running different operating systems, strict separation | Packaging and scaling many small services quickly |
Linux containers need a Linux kernel. On a Mac or Windows laptop, Docker Desktop quietly starts a small Linux VM and runs your containers inside it. That is why file sharing between your laptop and a container can feel slower there than on a real Linux server.
The best of both worlds
Containers and virtual machines are not enemies. Most cloud companies use both. Amazon’s AWS Fargate, for example, runs each customer task inside its own lightweight virtual machine built with a technology called Firecracker. The customer still thinks in containers, while AWS gets the strong wall of a VM between different customers. You will see this idea of layering again in the security chapter.
3Core Concepts You Must Know
Eight small ideas explain almost everything in Docker. Learn these words and the rest becomes easy to follow.
Image
A read-only package holding your app, its libraries and a minimal set of operating system files. Think of it as a frozen recipe card plus all the pre-measured ingredients.
Container
A live, running copy made from an image. If the image is the recipe, the container is the cake that comes out of the oven. You can bake many cakes from one recipe.
Dockerfile
A plain text file listing the steps to build an image, in order. It works like the numbered steps on a flat-pack furniture leaflet.
Layer
Each build step adds one layer of file changes. Layers stack like transparent sheets on an overhead projector, and the final picture is what you see through all of them.
Registry
An online storage place for images, such as Docker Hub or Amazon ECR. It works like a public library or app store where you borrow images and return new ones.
Tag and digest
A tag is a friendly, movable label such as “1.2”. A digest is a long, fixed fingerprint of the exact image contents. A tag is a name on a sticky note; a digest is a fingerprint.
Volume
A storage area managed by Docker that lives outside the container, so data survives when the container is deleted. It is like a storage unit you rent next to your apartment.
Network
A virtual network that lets containers talk to each other and to the outside world, like the corridors and front door of an apartment building.
Image versus container, the most important pair
Beginners often mix these up, so let us be very clear. An image never changes once it is built. It is only a file collection sitting on disk. When you ask the engine to run an image, it adds a thin writable layer on top and starts a process. That running package is the container. Delete the container and the image is still there, unchanged, ready to make a new one.
Programmers may recognise this pattern: an image is like a class, and a container is like an object created from that class. If you do not write code, the recipe-and-cake picture works just as well.
Tags can be moved. Today the tag “latest” might point to one image, and next month to a completely different one. A digest never moves, because it is calculated from the image content itself. When you need to be sure you are running exactly the same thing everywhere, use the digest.
4Architecture and Components
The engine is not one big lump. It is a chain of small programs, each with one clear job.
When you type a Docker command, your request travels through a short chain of programs before a container appears. The diagram below shows the chain from left to right.
flowchart LR
CLI["Docker CLI: the docker command"] -->|"REST API calls"| API["Engine API: Unix socket or secured TCP"]
API --> D["dockerd: the Docker daemon"]
D --> BK["BuildKit: the image builder"]
D --> NET["Networking: bridges and firewall rules"]
D --> VOL["Volumes: persistent storage"]
D -->|"gRPC"| C["containerd: container supervisor"]
C --> SH["containerd-shim: one per container"]
SH --> R["runc: OCI runtime"]
R --> P["Your app process inside the container"]
D -->|"pull and push images"| REG[("Registry: Docker Hub or Amazon ECR")]
Reading the diagram
On the left, the Docker CLI (command-line interface) is the program you type into. It does almost no real work. It packages your request and sends it over the Engine API. By default this message travels through a local Unix socket, which is a special file that programs on the same machine use to talk to each other. The engine can also listen on a network port, but that must be protected with encryption and login checks, as you will see in the security chapter.
The daemon, dockerd, is the manager. It keeps track of images, containers, networks and volumes. It also talks to registries when images must be downloaded or uploaded. But the daemon does not start containers by itself. It hands that job down the chain to a helper called containerd.
containerd (say “container-dee”) is a container supervisor. It pulls image content, stores it in a way the system can use, and manages the life of every container. It is an independent project that Kubernetes also uses directly. For each container it launches a tiny program called the shim. The shim stays alive as long as the container does. It keeps the container’s input and output connected and remembers the exit code when the container stops. Thanks to the shim, you can restart the daemon for an upgrade while containers keep running, if the live-restore setting is on.
runc is the worker at the very end. It follows the Open Container Initiative runtime standard. It asks the Linux kernel to build the private space, applies the safety limits, and starts your app. Once the app is running, runc’s job is done and it exits.
Imagine a busy restaurant. The Docker CLI is the waiter who takes your order. The Engine API is the order slip. The daemon is the restaurant manager who decides what needs to happen and checks the pantry. containerd is the kitchen supervisor who assigns each dish. The shim is the assistant who stays beside each dish until it is served. And runc is the chef who actually cooks it, then steps away.
Other pieces around the core
BuildKit
Turns a Dockerfile into an image. It can run independent steps at the same time and remember earlier results to save time.
Buildx and Compose
Add-on commands. Buildx builds for several processor types at once, and Compose starts several containers together from one file.
Swarm mode
A built-in way to join several machines into one group and spread containers across them. Many teams now prefer Kubernetes for this job.
Storage drivers
The part that stacks image layers into one working file system. The default on modern Linux is called overlay2.
One more point matters for the rest of this guide. Every link in the chain follows an open standard: the OCI image and runtime specifications. That is why an image built by Docker can run under containerd, Podman or Kubernetes, and why you are never locked into a single tool.
5How Isolation Works Inside the Kernel
Containers feel like separate computers, but the trick is done with three ordinary Linux features working together.
A container is not magic. It is a normal Linux process that the kernel has been told to treat specially, using three ingredients: namespaces, control groups, and a security layer.
Namespaces: giving each container its own view
A namespace is a Linux feature that gives a process its own private view of one type of resource, while hiding everything else on the machine. There are several kinds. The PID namespace makes a container see only its own processes, so its first process looks like process number 1, even though the host sees it as, say, process number 4522. The network namespace gives the container its own private network card, IP address and routing table. The mount namespace gives it its own view of the file system, so it cannot see the host’s files unless they are deliberately shared. There are also namespaces for host names, for inter-process communication, and for user ID numbers.
Control groups: sharing resources fairly
Control groups, usually shortened to cgroups, are a Linux feature that limits and measures how much processor time, memory and disk activity a process may use. Without cgroups, one busy container could use all the memory on a server and starve every other container next to it. With a memory cgroup limit set, the kernel simply stops that container from allocating more, and the container may be safely stopped and restarted instead of freezing the whole machine.
A security layer on top
On top of namespaces and cgroups, the engine can apply extra rulebooks such as seccomp, which blocks dangerous low-level requests a container is allowed to make to the kernel, and AppArmor or SELinux, which restrict what files and actions a container’s processes may touch. These are covered in more depth in the security chapter.
Namespaces are like giving each tenant of an apartment building their own numbered mailbox and their own door key, so they only see their own mail and their own rooms. Control groups are like the building’s electrician setting a fuse for each apartment, so no single apartment can trip the power for the whole building by running too many appliances at once.
Containers are not tiny virtual machines. They share the host’s actual kernel. This is exactly why they are fast and light, and also why isolation between containers is weaker than between virtual machines — a serious flaw in the shared kernel could, in theory, be used to see or affect another container. The security chapter explains how real deployments manage this risk.
6Images and Layers
Understanding layers explains why Docker builds and downloads are usually so fast after the first time.
An image is built from a text file called a Dockerfile. Each instruction in that file, such as copying in your code or installing a package, produces one read-only layer. The final image is simply an ordered stack of these layers, plus a small file called a manifest that lists them.
flowchart TB
L1["Layer 1: base operating system files"] --> L2["Layer 2: system packages installed"]
L2 --> L3["Layer 3: application dependencies"]
L3 --> L4["Layer 4: application source code"]
L4 --> L5["Writable container layer: created only when a container starts"]
Layers are stored once and reused. If ten different images all start from the same base layer, that layer is downloaded and stored on disk only once, and every image simply points to it. This is why pulling a second image that shares a base with one you already have often finishes in seconds instead of minutes.
Layers are also the reason build order matters. Docker checks whether an instruction and the files it depends on have changed since the last build. If nothing changed, it reuses the old layer instantly instead of redoing the work; this is called the build cache. Because of this, experienced teams place instructions that rarely change, like installing system packages, near the top of the Dockerfile, and instructions that change constantly, like copying in the latest source code, near the bottom. That way, an ordinary code change only rebuilds the last one or two layers.
How a container’s writable layer works
When a container starts, the engine adds one thin, writable layer on top of the image’s read-only layers, using a technique called copy-on-write. If the container’s process wants to change a file that exists in a lower, read-only layer, the storage driver copies that file up into the writable layer first, then changes the copy. The original layer, and every other container sharing it, is untouched. This is exactly why deleting a container does not affect the image or any other container built from it, and why many containers can share one image’s files in memory and on disk at the same time.
Layers are like tracing paper sheets stacked on top of a printed map. The bottom sheets are locked and shared by everyone. Your own sheet on top is the only one you may draw on. If you want to change a road that is printed on a locked sheet, you first trace a fresh copy of just that road onto your own sheet, then edit your copy. The original map underneath never changes.
One image, many processor types
Modern images can also be multi-architecture, meaning a single image name actually points to several different builds behind the scenes, one for each processor family such as the common Intel and AMD design or the Arm design used in many phones and newer laptops. When you pull an image, the engine automatically selects the correct build for your own machine, using a small index file called a manifest list. This is what lets the same docker pull command work correctly whether it is run on a traditional data-centre server or on a newer Arm-based cloud instance, without the person typing the command needing to know or care which processor family is underneath.
7Data Flow and Lifecycle
Follow one command from your keyboard all the way to a running, and later a stopped, container.
sequenceDiagram
participant U as User
participant CLI as Docker CLI
participant API as Engine API
participant D as dockerd
participant C as containerd
participant R as runc
U->>CLI: docker run myapp
CLI->>API: POST create + start container
API->>D: forward request
D->>D: check image locally, pull if missing
D->>C: create and start task
C->>R: run OCI bundle
R->>R: build namespaces and cgroups
R-->>C: process started, runc exits
C-->>D: container running
D-->>API: success
API-->>CLI: container ID
CLI-->>U: prints container ID
When you run docker run myapp, the CLI first checks whether an image called myapp already exists on your machine. If not, the daemon downloads, or pulls, it from a registry, layer by layer, skipping any layers already present locally. The daemon then asks containerd to create a container from that image, containerd asks runc to actually start the process inside fresh namespaces and cgroups, and runc exits once the process is running, leaving the shim to watch over it.
The states a container passes through
Created
The container exists with its file system prepared, but its main process has not started yet.
Running
The main process is active. This is the normal working state.
Paused
All processes are temporarily frozen in memory using a cgroup freezer, without being stopped.
Stopped or exited
The main process has ended, either normally or because it crashed, and its exit code is recorded.
Removed
The container’s writable layer and metadata are deleted. Anything not saved to a volume is now gone for good.
Any file your app writes inside the container’s own writable layer disappears when the container is removed. Anything you truly need to keep, such as a database’s files or uploaded content, should be written to a volume instead, which is covered in the storage chapter.
8Networking Between Containers
Containers need to talk to each other and to the outside world. The engine gives each container its own virtual network card and several ways to connect them.
By default, the engine creates a private virtual network called bridge on the host. Every new container that does not ask for something different is attached to this bridge and given its own private IP address, similar to how a home Wi-Fi router hands out addresses to phones and laptops. Containers on the same bridge can reach each other by IP address, and if you create your own named bridge network, they can also reach each other simply by container name, because the engine runs a small internal DNS server for you.
Bridge network
A private virtual network on one host. Good for most single-machine setups.
Host network
The container shares the host’s own network stack directly, skipping translation for extra speed, at the cost of isolation.
Overlay network
A virtual network that spans several physical machines, used by swarm mode and similar cluster tools.
None
The container gets no network access at all, useful for isolated batch jobs.
Reaching the outside world
A container’s private IP address is not reachable from outside the host by default. To let outside traffic in, you publish a port, for example telling Docker to send anything arriving on the host’s port 8080 to port 80 inside the container. Behind the scenes, the engine sets up network address translation rules using the host’s firewall so that traffic is quietly redirected to the right container.
The bridge network is like the internal phone extension system inside an office building; any desk can dial any other desk’s extension. Publishing a port is like assigning extension 205 to be reachable from an outside phone line dialled at the building’s public number, so a customer can call in without knowing the internal wiring.
A less common but useful option is the macvlan driver, which gives a container its own genuine hardware-level address on the physical network, making it appear to the rest of the building’s network as if it were a separate physical device rather than something hidden behind the host. This is handy for older applications that expect to own a real network address directly, but it is used far less often than the default bridge network, since it also means the host’s usual firewall protections no longer sit between the container and the network.
9Storage and Volumes
Containers are meant to be thrown away and rebuilt often, so anything that must survive needs a home outside the container.
A volume is a storage area managed entirely by the Docker Engine, stored on the host’s disk but kept outside any single container’s writable layer. When a container writes to a location where a volume is attached, that data is really being written into the volume, and it stays there even after the container is stopped, removed, or replaced by a newer version.
Named volume
Fully managed by Docker, easy to back up, and the safest default choice for databases and other stateful apps.
Bind mount
Maps a specific folder on the host straight into the container. Common for live-editing source code during development.
tmpfs mount
Stores data only in the host’s memory, never on disk. Fast, but everything is lost when the container stops.
If you run a database inside a container without attaching a volume, every row you insert lives only in that container’s temporary writable layer. The moment someone removes that container to upgrade the image, all of that data is gone permanently. Always attach a volume for anything that must outlive the container.
Volumes can also be shared between several containers at once, which is how, for example, a web server container and a separate log-collecting container can both read and write the same log files without copying them back and forth.
Volume drivers and cloud storage
On a single machine, a volume is normally just a managed folder on the local disk. However, the engine also supports pluggable volume drivers, which let a volume actually be backed by something other than local disk, such as a network file share or a cloud storage service. This matters most once containers move across several machines in a cluster: if a container with important data is rescheduled from one server to another, a local-only volume would be left behind, but a volume backed by shared network or cloud storage can follow the container to its new home. This is one of the details orchestrators such as Kubernetes handle for you through their own storage systems, building on the same underlying volume idea introduced here.
10Advantages, Disadvantages and Trade-offs
No technology is free of trade-offs. Knowing the honest downsides makes you a stronger engineer, not a weaker one.
Advantages
- Consistent behaviour from a laptop to a production server, because the same image runs everywhere
- Fast start-up, often under a second, which suits scaling up and down quickly
- Efficient use of hardware, since many containers share one kernel instead of each needing a full operating system
- A huge ecosystem of ready-made images and tools built around one open standard
- Clear packaging of an app’s exact dependencies, ending most “works on my machine” arguments
Disadvantages
- Weaker isolation than a virtual machine, because the kernel is shared between containers
- Linux containers need a Linux kernel, so Mac and Windows users run an extra hidden virtual machine
- Running many containers well at scale usually needs an orchestrator such as Kubernetes, adding its own complexity
- Persistent data needs deliberate planning with volumes, or it is easily lost by accident
- Badly written images can be very large or contain outdated, vulnerable software if not maintained
11Security
Because containers share a kernel, security has to be built in layers rather than relied on as one single wall.
Built-in defences
Seccomp profiles
Block a container from making dangerous, rarely needed requests directly to the kernel, shrinking the space an attacker could exploit.
AppArmor / SELinux
Mandatory access control systems that restrict which files, network actions and capabilities a container’s processes may use, even if the app itself is compromised.
Linux capabilities
Root inside a container normally has a reduced set of powers compared with real root on the host, and extra capabilities can be dropped further.
User namespaces
Can remap root inside a container to an unprivileged, ordinary user ID on the host, so a break-out gains far less.
Practices every team should follow
- Run the process inside the container as a non-root user whenever the application allows it.
- Keep base images small and current, since fewer installed packages means fewer known vulnerabilities to patch.
- Scan images for known vulnerabilities before they are deployed, using a vulnerability scanner as part of the build pipeline.
- Sign and verify images so a team can trust that the image running in production is exactly the one that was built and tested.
- Never mount the host’s own Docker socket into a container casually, since a process with access to it can effectively control every container on that host.
- Protect the Engine API. If it must be reachable over the network, always require mutual TLS encryption and authentication rather than leaving it open.
The situation
A single shared kernel means one deep flaw could, in theory, let a process escape its container and affect its neighbours.
The response
Serious deployments stack several independent barriers rather than trusting one: non-root users, seccomp and capability restrictions, network policies that limit which containers may even talk to each other, and for the highest-risk workloads a stronger boundary such as a lightweight virtual machine per container, as AWS Fargate does with Firecracker.
Why it works
Even if an attacker defeats one layer, the remaining layers still have to be broken separately, which sharply raises the cost and difficulty of a successful attack.
Teams sometimes run containers with the --privileged flag “just to make an error go away.” This flag removes almost all of the isolation described in this guide. It should be treated as a last resort for very specific, trusted tooling, never as a routine fix.
12Monitoring, Logging and Metrics
You cannot fix what you cannot see. The engine gives you basic visibility out of the box, and larger systems build much more on top.
Every container’s standard output and standard error, the two text streams a program normally prints to, are captured automatically by the engine through a logging driver. The default driver simply writes these streams to local files in JSON format, which the docker logs command reads back for you. In real production systems, teams usually switch to a driver that forwards logs straight to a central logging platform, so that logs from hundreds of containers across many servers can be searched in one place instead of one host at a time.
For live resource usage, the docker stats command shows processor use, memory use, network traffic and disk activity for running containers, pulled directly from the same cgroup counters the kernel already keeps. This is useful for a quick manual check, but production systems usually export these numbers continuously to a metrics platform such as Prometheus, so trends can be graphed and alerts can fire automatically when something drifts out of range.
| Signal | What it tells you | Typical tool |
|---|---|---|
| Logs | What the application said happened, in order | Centralised logging platform |
| Metrics | Numeric trends over time, such as CPU or request count | Prometheus and a graphing dashboard |
| Health checks | Whether the app inside the container is actually working, not just running | Docker’s built-in HEALTHCHECK instruction |
| Events | Life-cycle changes such as a container starting, stopping or dying | docker events stream |
A container can be in the “running” state while the application inside it is frozen or broken. A Dockerfile’s HEALTHCHECK instruction lets the engine periodically run a small test command inside the container, such as asking a web server for a page, and mark the container unhealthy if that test keeps failing, so automated systems can restart or replace it.
As the number of containers grows, teams usually add one more layer of visibility on top of logs and metrics: distributed tracing. A single user request in a modern application might pass through a dozen small services, each running in its own container, before a response is sent back. Tracing tools attach a shared identifier to that request as it travels, so that when something is slow or fails, an engineer can see the exact path the request took across every container involved, rather than searching through a dozen separate log files by hand and guessing which ones are related.
13Deployment and the Cloud
A single Docker Engine on one laptop is where most people start. Real products usually run across many machines, coordinated by an orchestrator.
An orchestrator is a system that decides which container runs on which machine, restarts containers that fail, and spreads traffic across many copies of the same app. Docker ships its own simple orchestrator, called swarm mode, built directly into the engine. Most large organisations instead choose Kubernetes, a separate, more feature-rich orchestrator that has become the industry standard, even though it no longer talks to the Docker Engine directly and instead uses containerd or a similar runtime underneath.
flowchart TB
Dev["Developer writes Dockerfile"] --> Build["CI pipeline builds image"]
Build --> Push["Image pushed to a registry"]
Push --> Orc["Orchestrator schedules containers across a cluster"]
Orc --> N1["Node 1: containerd + runc"]
Orc --> N2["Node 2: containerd + runc"]
Orc --> N3["Node 3: containerd + runc"]
Every major cloud provider offers managed container services built on these same open standards. Amazon Web Services offers Amazon ECS, its own orchestrator, and Amazon EKS, a managed version of Kubernetes, alongside AWS Fargate, which runs containers without the customer having to manage any servers at all. Google Cloud and Microsoft Azure offer comparable managed Kubernetes services of their own. In every case, the image you built with the Docker Engine on your laptop is the same image these cloud services run, because they all speak the same open container format.
Continuous delivery in practice
A typical modern pipeline works like this: a developer pushes code, an automated build server builds a new image and tags it with a version number, a vulnerability scanner checks it, the image is pushed to a registry, and the orchestrator gradually replaces old containers with new ones, often a few at a time, so that if something is wrong, only a small slice of traffic is affected before the rollout is stopped automatically.
GitOps: letting the cluster read its own instructions
Many teams take this one step further with an approach called GitOps. Instead of an engineer running deployment commands by hand, the desired state of the whole system, which images should be running and how many copies of each, is written down as plain configuration files stored in a version-controlled repository, the same kind of system used to store source code. A small automated controller inside the cluster continuously compares that stored description with what is actually running, and quietly corrects any difference it finds. This means every change to production has a written history, can be reviewed before it happens, and can be undone simply by reverting the configuration file to an earlier version.
14Design Patterns and Anti-patterns
Certain shapes of using containers work well again and again. Others cause pain that looks different every time but has the same root cause.
One process per container
Each container runs a single main responsibility, such as the web server or the database, making it simple to scale, replace or restart independently.
Sidecar
A helper container runs alongside the main one to add a capability such as log shipping or traffic encryption, without changing the main application’s code.
Multi-stage build
A Dockerfile uses one stage with heavy compiler tools to build the app, then copies only the finished result into a small, clean final image, keeping it lean.
Immutable image
Once built and tagged, an image is never changed in place. A fix always means building and deploying a brand-new image.
What it looks like
A single container running a web server, a database, a scheduled job and a message queue all at once, because it felt simpler to set up that way at first.
Why it hurts later
The whole bundle must be restarted together even if only one piece needs a restart. It cannot be scaled piece by piece, since scaling the container scales everything inside it equally. And a crash in one piece can bring down the log output and health checks for all the others, making the real cause hard to find.
The fix
Split each responsibility into its own container, connect them with a network, and let an orchestrator manage and scale each one on its own.
What it looks like
Baking a database password or an API key directly into a Dockerfile instruction or into the application’s source code before building the image.
Why it hurts later
Every layer of an image is easy to inspect, and the image is often pushed to a shared registry. Anyone who can pull the image can typically recover that secret, even from an old layer that a later instruction appeared to remove.
The fix
Pass secrets in at container start-up time, through a secrets manager or an orchestrator’s built-in secrets feature, so they never become part of the image itself.
15Best Practices and Common Mistakes
A short, practical checklist that catches most of the problems beginners run into.
Use small base images
Choosing a minimal base such as Alpine or a “slim” variant shrinks build time, download time and the space for vulnerabilities to hide.
Order Dockerfile steps well
Put rarely changing steps first and frequently changing steps, like copying source code, last, so the build cache does more work for you.
Pin versions
Referencing an exact image version or digest, rather than a moving tag like “latest,” avoids surprise changes when a base image updates.
Set resource limits
Giving every container a memory and CPU limit prevents one runaway process from starving its neighbours on the same host.
Clean up unused data
Old images, stopped containers and unused volumes quietly consume disk space; regular cleanup keeps hosts healthy.
Write a .dockerignore file
Excludes files such as local development folders and secrets from ever being sent to the build process in the first place.
When the engine stops a container, it sends a polite termination signal first and only forcibly kills the process after a short grace period. An application that ignores this signal loses in-flight requests and can corrupt data on every deployment. Well-behaved applications listen for this signal and shut down cleanly within the grace period.
16Real-World and Industry Examples
These ideas are not theoretical. Some of the largest technology companies in the world run on containers every single day.
Netflix
Runs a large share of its backend services in containers scheduled across its own infrastructure, letting it deploy thousands of small services independently and scale them separately during peak viewing hours.
Amazon
Built Amazon ECS and Amazon EKS specifically to run customer containers at massive scale, and created Firecracker, the lightweight virtualisation technology that also powers AWS Lambda and Fargate.
Uber
Moved from a single large application to hundreds of independently deployable services, many running in containers, to let separate teams ship updates without waiting on each other.
Runs essentially all of its internal services in containers and originally built the ideas behind Kubernetes from over a decade of running containers internally at enormous scale.
A common thread across all of these companies is the same one described at the start of this guide: containers let a huge number of independent teams package their own piece of software once and trust that it will run the same way everywhere it is deployed, from a laptop to a global fleet of servers.
17Frequently Asked Questions
18Summary and Key Takeaways
Carry these ideas forward
- The Docker Engine is a client-server system: a lightweight CLI you type into, an API in between, and a daemon that does the real work through containerd and runc.
- Images are frozen blueprints; containers are the running copies made from them. Deleting a container never touches the image it came from.
- Isolation is built from ordinary Linux features — namespaces for a private view of the system, and control groups for fair sharing of resources — not from a separate pretend computer like a virtual machine.
- Layers make images fast to build and download by reusing anything unchanged, and copy-on-write keeps a running container’s changes separate from its shared, read-only image.
- Data written inside a container disappears when it is removed unless it lives in a volume, so plan storage deliberately from the start.
- Security is a stack of defences, not one wall: non-root users, seccomp, capability limits and, for the highest-risk workloads, an even stronger boundary such as a lightweight VM.
- At scale, an orchestrator such as Kubernetes or Docker’s own swarm mode decides where containers run, restarts failures, and spreads traffic — and every major cloud provider builds managed services on the same open container standards.