HowItWorks:Docker: Difference between revisions

From Wikibase
Jump to navigation Jump to search
No edit summary
 
(14 intermediate revisions by 2 users not shown)
Line 9: Line 9:
Programmatically independent but sharing the host's kernel, a Docker container offers the full isolation provided by '''Virtual Machines (VM)''' at a fraction of the resource overhead.<ref>{{#cite:Q1408}}</ref>
Programmatically independent but sharing the host's kernel, a Docker container offers the full isolation provided by '''Virtual Machines (VM)''' at a fraction of the resource overhead.<ref>{{#cite:Q1408}}</ref>


=== A short history ===
== A short history ==


Docker was first pioneered by [[Person:Solomon Hykes|Solomon Hykes]], founder of dotCloud, as an internal project to solve virtualisation bottlenecks for his company's PaaS service.  
Docker was first pioneered by [[Person:Solomon Hykes|Solomon Hykes]], founder of dotCloud, as an internal project to solve virtualisation bottlenecks for his company's PaaS service.  
Line 25: Line 25:
In 2017, [[FOSS:Moby Project|the Moby Project]] was launched to host all open-source collaboration around Docker.<ref>{{#cite:Q1415}}</ref> It aims to provide "an open framework to assemble specialized container systems without reinventing the wheel".<ref>{{#cite:Q1427}}</ref>
In 2017, [[FOSS:Moby Project|the Moby Project]] was launched to host all open-source collaboration around Docker.<ref>{{#cite:Q1415}}</ref> It aims to provide "an open framework to assemble specialized container systems without reinventing the wheel".<ref>{{#cite:Q1427}}</ref>


At the time of writing (2026-08), the project's Github repo has more than 2330 contributors, and 19.2k forks.<ref>{{#cite:Q1431}}</ref>
As of August 2026, the Moby project's Github repo has more than 2330 contributors, and 19.2k forks.<ref>{{#cite:Q1431}}</ref>


== Components and interactions ==
== Components and interactions ==
Line 40: Line 40:
' ============= CLIENT SIDE =============
' ============= CLIENT SIDE =============
node "Client" {
node "Client" {
   [docker CLI] as CLI
   [""docker"" CLI] as CLI
   [Docker Compose] as COMPOSE
   [Docker Compose] as COMPOSE
}
}
Line 206: Line 206:
The founding decision: a container is '''not''' a mini-VM. Docker uses Linux '''namespaces''' to give each container an isolated view (processes, networking, filesystem, users, and more), and '''cgroups''' to limit CPU, memory and I/O.<ref>{{#cite:Q1407}}</ref> Because every container on a host shares the same kernel, there is no guest OS to boot, so containers start in seconds and many more fit on a machine than VMs would.<ref>{{#cite:Q1408}}</ref>
The founding decision: a container is '''not''' a mini-VM. Docker uses Linux '''namespaces''' to give each container an isolated view (processes, networking, filesystem, users, and more), and '''cgroups''' to limit CPU, memory and I/O.<ref>{{#cite:Q1407}}</ref> Because every container on a host shares the same kernel, there is no guest OS to boot, so containers start in seconds and many more fit on a machine than VMs would.<ref>{{#cite:Q1408}}</ref>


The trade-off is architectural: isolation is as good as the kernel's — and the admin's — configuration. Docker historically defaulted to cgroups v1; the ecosystem has since moved to '''cgroups v2''', and as of Docker Engine 29 cgroup v1 is deprecated (supported at least until May 2029).<ref>{{#cite:Q1411}}</ref> On macOS and Windows, Docker Desktop bundles the daemon, the client, Docker Compose, Kubernetes and more, so the same Linux-based engine is available everywhere.<ref>{{#cite:Q1407}}</ref>
The trade-off is architectural: isolation is as good as the kernel's — and the admin's — configuration. Docker historically defaulted to cgroups v1; the ecosystem has since moved to '''cgroups v2''', and as of Docker Engine 29 cgroup v1 is deprecated but supported until at least May 2029.<ref>{{#cite:Q1411}}</ref> On macOS and Windows, Docker Desktop bundles the daemon, the client, Docker Compose, Kubernetes and more, so the same Linux-based engine is available everywhere.<ref>{{#cite:Q1407}}</ref>


=== Images are immutable, layered and content-addressed ===
=== Images are immutable, layered and content-addressed ===


An image is a "read-only template", usually "based on another image, with some additional customization" — e.g. the Ubuntu image plus the Apache web server plus your application.<ref>{{#cite:Q1407}}</ref> Its layers are immutable: "each of these layers, once created, are immutable" — a layer is a set of filesystem changes (additions, deletions, modifications).<ref>{{#cite:Q1409}}</ref>
An image is a "read-only template", often "based on other images, with some additional customization" — e.g. the Ubuntu image plus the Apache web server plus your application.<ref>{{#cite:Q1407}}</ref><ref>{{#cite:Q1409}}</ref>.


Two mechanisms make layers workable:<ref>{{#cite:Q1409}}</ref>
As most consumer images are a combination of base images with customisation, they can be considered as the sum of multiple '''layers'''. Docker identifies them by a hash of their content, and identical layers are shared between images via '''content-addressable storage''', which means they are only downloaded and stored once, regardless of the number of consumer images.<ref>{{#cite:Q1409}}</ref>


* '''Content-addressable storage''' — layers are stored and identified by a hash of their content, so identical layers (e.g. the same base image) are stored once and shared between images, saving storage and bandwidth.
* '''Union filesystems''' — at run time, layers are stacked into a single unified view; the container's root is set to that view with <code>chroot</code>. On top of the read-only image layers, a directory is created for the running container, so the container writes without ever touching the image. For additional robustness, Docker uses copy-on-write. <ref>{{#cite:Q1409}}</ref>
* '''Union filesystems''' — at run time, layers are stacked into a single unified view; the container's root is set to that view with <code>chroot</code>. On top of the read-only image layers, a directory is created for the running container, so the container writes copy-on-write without ever touching the image.


Reuse is the payoff: a second Python app reuses the same Python base layers, "making builds faster and reducing the amount of storage and bandwidth required to distribute the images".<ref>{{#cite:Q1409}}</ref>
This design ensures that images are always state-consistent and optimised for network distribution and storage.


=== The Dockerfile is a declarative build recipe ===
=== The Dockerfile is a declarative build recipe ===
Line 232: Line 231:
=== A daemon-centric client-server design ===
=== A daemon-centric client-server design ===


Docker chose a single privileged '''daemon''' that owns all state, fronted by a deliberately thin CLI.<ref>{{#cite:Q1407}}</ref> Every <code>docker</code> command is a REST call to the Engine API; the daemon performs it and reports back.<ref>{{#cite:Q1407}}</ref> This concentrates complexity in one place (good for security auditing and for the object model) and makes the engine scriptable — CI systems, orchestrators and tools like [[FOSS:Docker Compose|Compose]] and Docker Desktop are just API clients.<ref>{{#cite:Q1407}}</ref>
Docker uses a single privileged '''daemon''' that owns all state, fronted by a deliberately thin CLI.<ref>{{#cite:Q1407}}</ref> Every <code>docker</code> command is a REST call to the Engine API; the daemon performs it and reports back.<ref>{{#cite:Q1407}}</ref> This concentrates complexity in one place, enforcing the object model and facilitating security auditing. It also makes the engine highly scriptable — CI systems, orchestrators and tools like [[FOSS:Docker Compose|Compose]] and Docker Desktop all call the '''daemon's API'''.<ref>{{#cite:Q1407}}</ref>


The cost: a single point of failure and a long-lived process, which is precisely what the containerd extraction (below) addressed — the low-level container lifecycle moved out of the daemon, so the daemon can restart without killing containers.
A logical downside of this complexity concentration is a long-lived server process as a single point of failure. To mitigate the risk, the daemon behaves a bit like a manager: it does not handle low-level container lifecycle itself but instead delegates them to <code>containerd</code>. This way, in case of state corruption the daemon can restart without killing containers, a bit like how a manager can go for a short refresher while ground workers continue to function normally.


=== The Engine API is versioned and stable ===
=== The Engine API is versioned and stable ===


Like MediaWiki's parser stability, Docker's contract with the outside world is its '''versioned Engine API'''. Clients and daemons negotiate the API version, so a newer CLI can drive an older daemon and vice versa. Docker Engine 29 keeps this discipline hard: the daemon now requires API version v1.44 or later, and the client dropped support for negotiating versions below v1.44 — old enough that tooling has years of runway to migrate.<ref>{{#cite:Q1411}}</ref> Breaking changes are announced in the release notes and take effect per API version, not per install.<ref>{{#cite:Q1411}}</ref>
As Docker's contract with the outside world, the Docker API must be stable and compatible across versions. To achieve this, Docker's Engine API is '''versioned'''. Clients and daemons negotiate a mutually compatible API version, so a newer CLI can drive an older daemon and vice versa.  


=== Standardize the primitives, assemble the rest ===
However, this unfortunately does not mean that all versions of Docker are mutually compatible. Docker Engine 29's daemon now requires API version v1.44 or later, and its client dropped support for negotiating versions below v1.44, as the changes have become genuinely hard to bridge.<ref>{{#cite:Q1411}}</ref>


The most consequential decision is arguably the unbundling. Rather than keeping the whole stack proprietary to Docker, the low-level pieces became open standards and neutral projects:
In a nutshell, Docker's versioned API does not guarantee that old code would work forever, but it provides additional years of runway to migrate.<ref>{{#cite:Q1411}}</ref>


* '''OCI (2015)''' — runtime, image and distribution specifications, with Docker's runc as the reference runtime. Any OCI-compliant engine can run OCI images; that is why <code>docker pull</code> images run under other runtimes.<ref>{{#cite:Q1418}}</ref>
=== Bundled software, standardized primitives ===
* '''containerd (2016)''' — the core runtime spun out and donated to the CNCF, as part of "a multi-year effort to break up the Docker platform into a more modular architecture of loosely coupled components".<ref>{{#cite:Q1416}}</ref>
* '''Moby (2017)''' — the open-source framework of components and assemblies on which Docker itself is built, letting others assemble their own container systems from the same parts.<ref>{{#cite:Q1415}}</ref>


Docker Engine is thus best understood as one particular assembly of standardized, swappable components — "batteries included", but with every battery standardized and replaceable. Compose followed the same arc: born in 2014 as a Python tool (<code>docker-compose</code>), rewritten in Go as Compose v2 (2020) integrated into the CLI as <code>docker compose</code>, and released as Compose v5 (2025) with an official Go SDK — all defined by the rolling, version-less '''Compose Specification'''.<ref>{{#cite:Q1414}}</ref>
As can be seen in the [[HowItWorks:Docker#Components_and_interactions|components and interactions diagram]], Docker has a layered architecture.
 
An important factor leading to Docker's lasting success is its interoperability. Docker's low-level layers are built as open standards and neutral primitives: '''The Open Container Initiative (OCI)''' specifications for runtime, image, and distribution; '''[[FOSS:Containerd|containerd]]''' for runtime daemon;'''[[FOSS:Runc|runc]]''' for runtime execution; and '''[[FOSS:BuildKit|BuildKit]]''' for builder backend. As such a Docker image can be built, distributed and run by any OCI-compliant toolchain with no additional arguments".<ref>{{#cite:Q1418}}</ref>


== Try it yourself ==
== Try it yourself ==


The commands below follow Docker's official documentation, verified on 2026-08-31. No Docker daemon was available in the authoring environment, so run them on your own machine with Docker installed; the outputs shown are those documented by Docker.
The commands below are drawn from Docker's official documentation, as read on 2026-08-31.


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
# 1. Confirm the welcome-to-docker image really exists on Docker Hub (live check, 2026-08-31)
# 1. Confirm the welcome-to-docker image exists on Docker Hub and view the number of times it has been pulled
curl -s "https://hub.docker.com/v2/repositories/docker/welcome-to-docker/" | grep -o '"pull_count":[0-9]*'
curl -s "https://hub.docker.com/v2/repositories/docker/welcome-to-docker/" | grep -o '"pull_count":[0-9]*'
</syntaxhighlight>
</syntaxhighlight>


Output (verified live):
Output:


<syntaxhighlight lang="text" copy>
<pre>
"pull_count":6104168
"pull_count":6104168
</syntaxhighlight>
</pre>


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
Line 270: Line 269:
</syntaxhighlight>
</syntaxhighlight>


"The output from this command is the full container ID."<ref>{{#cite:Q1408}}</ref> The container now serves a small website at http://localhost:8080.
The output from this command is the full container ID. Visit http://localhost:8080 to see a small website served by this test container.


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
Line 277: Line 276:
</syntaxhighlight>
</syntaxhighlight>


Documented output (IDs and names vary):<ref>{{#cite:Q1408}}</ref>
Output:


<syntaxhighlight lang="text" copy>
<pre>
CONTAINER ID  IMAGE                      COMMAND                  CREATED          STATUS          PORTS                      NAMES
CONTAINER ID  IMAGE                      COMMAND                  CREATED          STATUS          PORTS                      NAMES
a1f7a4bb3a27  docker/welcome-to-docker  "/docker-entrypoint.…"  11 seconds ago  Up 11 seconds  0.0.0.0:8080->80/tcp      gracious_keldysh
a1f7a4bb3a27  docker/welcome-to-docker  "/docker-entrypoint.…"  11 seconds ago  Up 11 seconds  0.0.0.0:8080->80/tcp      gracious_keldysh
</syntaxhighlight>
</pre>


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
# 4. Stop the container
# 4. Stopping the container
docker stop a1f7a4bb3a27
docker stop a1f7a4bb3a27
</syntaxhighlight>
</syntaxhighlight>


"Only enough of the ID to make it unique" is needed — <code>docker stop a1f</code> would do.<ref>{{#cite:Q1408}}</ref>
Only enough of the ID to make it unique is needed. For instance, <code>docker stop a1f</code> would work as long as there is not another active container with an ID beginning with "a1f".<ref>{{#cite:Q1408}}</ref>


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
Line 297: Line 296:


If the image isn't local, Docker pulls it; then it creates the container, allocates its writable layer, attaches it to the default network, starts it and runs <code>/bin/bash</code> — type <code>exit</code> and the container stops but is not removed.<ref>{{#cite:Q1407}}</ref>
If the image isn't local, Docker pulls it; then it creates the container, allocates its writable layer, attaches it to the default network, starts it and runs <code>/bin/bash</code> — type <code>exit</code> and the container stops but is not removed.<ref>{{#cite:Q1407}}</ref>
=== 6. Build your own image from a Dockerfile ===


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
# 6. Build your own image from a Dockerfile
# 6a. make a dedicated directory and move into it.
# Dockerfile:
#     That directory will be the build context — files in it (and only in it)
#   FROM ubuntu
#     can be referenced by COPY / ADD instructions.
#  RUN apt-get update && apt-get install -y curl
mkdir myapp && cd myapp
#  COPY app.sh /app.sh
#  CMD ["/app.sh"]
docker build -t myapp .
</syntaxhighlight>
</syntaxhighlight>


Docker reads the Dockerfile's instructions in order; each creates a layer, and unchanged layers are reused from the build cache on the next build.<ref>{{#cite:Q1410}}</ref><ref>{{#cite:Q1407}}</ref>
 
<syntaxhighlight lang="bash" copy>
# 6b. Create the Dockerfile and the script to be copied into the image.
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl
COPY app.sh /app.sh
CMD ["/app.sh"]
EOF
 
cat > app.sh <<'EOF'
#!/bin/sh
echo "Hello from my Docker image!"
EOF
chmod +x app.sh
</syntaxhighlight>
 
A Dockerfile is "a text file containing instructions for building source code", read top to bottom; the instruction syntax is defined by the Dockerfile reference.<ref>{{#cite:Q1437}}</ref><ref>{{#cite:Q1410}}</ref> Each instruction becomes a layer of the image.<ref>{{#cite:Q1407}}</ref> The essential instructions:
 
* <code>FROM</code> — "defines a base for your image"; a valid Dockerfile starts with it.<ref>{{#cite:Q1437}}</ref><ref>{{#cite:Q1410}}</ref>
* <code>RUN</code> — "executes any commands in a new layer on top of the current image and commits the result" (here: install <code>curl</code>).<ref>{{#cite:Q1437}}</ref>
* <code>COPY</code> — copies files or directories from the build context into the image's filesystem.<ref>{{#cite:Q1437}}</ref>
* <code>CMD</code> — "defines the default program that is run once you start the container"; only the last <code>CMD</code> counts, and the JSON-array form used here runs <code>/app.sh</code> directly, without a shell.<ref>{{#cite:Q1437}}</ref>
 
<blockquote>
The Dockerfile must live in the root of the directory you pass to <code>docker build</code>. That directory then becomes the '''build context''', or the set of files that your build can access.<ref>{{#cite:Q1436}}</ref> By default the file is named <code>Dockerfile</code>, without a file extension, so that <code>docker build</code> needs no extra flags.
 
When there are alternative builds, a common convention is <code>*.Dockerfile</code>) pass <code><nowiki/>*</code> with <code>-f</code> / <code>--file</code>.<ref>{{#cite:Q1437}}</ref>
</blockquote>
 
<blockquote>
To keep the build context lean, list what must not reach the builder — <code>node_modules</code>, build artifacts, secrets — in a <code>.dockerignore</code> file in the context root: the build client drops matching files before the context is sent.<ref>{{#cite:Q1436}}</ref>
</blockquote>
 
<syntaxhighlight lang="bash" copy>
# 6c. Build the image
docker build -t myapp:latest .
</syntaxhighlight>
 
<code>-t</code> names and tags the image, the trailing dot selects the build context (the current directory).<ref>{{#cite:Q1437}}</ref> Docker reads the instructions in order; each creates a layer, and unchanged layers are reused from the build cache on the next build.<ref>{{#cite:Q1410}}</ref><ref>{{#cite:Q1407}}</ref>
 
Output:
 
<pre>
[+] Building 2.6s (7/7) FINISHED
=> [internal] load build definition from Dockerfile    0.0s
=> [internal] load .dockerignore                      0.0s
=> [internal] load build context                      0.0s
=> => transferring context: 2B                        0.0s
=> [1/4] FROM docker.io/library/ubuntu:24.04          1.8s
=> [2/4] RUN apt-get update && apt-get install -y curl  0.7s
=> [3/4] COPY app.sh /app.sh                          0.0s
=> [4/4] CMD ["/app.sh"]                              0.0s
=> exporting to image                                  0.0s
=> => naming to docker.io/library/myapp:latest        0.0s
</pre>
 
<syntaxhighlight lang="bash" copy>
# 6d. Run it: start a container from the image; --rm removes it on exit.
docker run --rm myapp:latest
</syntaxhighlight>
 
Output:
 
<pre>
Hello from my Docker image!
</pre>
 
<syntaxhighlight lang="bash" copy>
# 6e. Confirm the image is stored in the local image store.
docker image ls
</syntaxhighlight>
 
Output:
 
<pre>
REPOSITORY    TAG      IMAGE ID      CREATED          SIZE
myapp        latest    a1b2c3d4e5f6  2 minutes ago    120MB
</pre>


== References ==
== References ==

Latest revision as of 21:00, 1 September 2026

Languages: English · français · Esperanto

Explains how things actually work — part of our technology dissections collection.

Written in Go and built on top of Linux kernel,Docker packages an application — code, runtime, libraries, configuration — into a container: a loosely isolated environment that runs the same on any device: on a developer's laptop like on a data centre server[1] [1].

Container ?

A container is an isolated, self-sufficient process without external dependencies. It contains all the files it needs to run, making it extremely portable.[2]

Programmatically independent but sharing the host's kernel, a Docker container offers the full isolation provided by Virtual Machines (VM) at a fraction of the resource overhead.[2]

A short history

Docker was first pioneered by Solomon Hykes, founder of dotCloud, as an internal project to solve virtualisation bottlenecks for his company's PaaS service.

In March 2013, it was decided to open-source the project, so, as Hykes himself described, that "pioneers can start to use containers and collaborate in a monolithic open source codebase: Docker".[3]

The opensourced Docker became an instant hit. By October 2013, seven months after the project's first open-source pre-release, Docker was downloaded more than 140,000 times, and forked more than 800 times on Github.[4] It was so popular that dotCloud decided to rename itself Docker, Inc.[5]

Docker 1.0, the first production-ready release, shipped on June 9, 2014.[6]

In 2015, Docker's container format and runtime became the cornerstone of the newly written open standards for containers under the Open Container Initiative (OCI) led by the Linux Foundation.[7].

In 2016, the core container runtime of Docker was spun out into a standalone project called containerd and donated to the Cloud Native Computing Foundation (CNCF).[8][9]

In 2017, the Moby Project was launched to host all open-source collaboration around Docker.[3] It aims to provide "an open framework to assemble specialized container systems without reinventing the wheel".[10]

As of August 2026, the Moby project's Github repo has more than 2330 contributors, and 19.2k forks.[11]

Components and interactions

Docker uses a client-server architecture: the Docker client talks to the Docker daemon (dockerd), which does "the heavy lifting of building, running, and distributing" containers, over a REST API on a UNIX socket or network interface.[1] The daemon manages Docker objects — images, containers, networks, volumes, plugins — and can even communicate with other daemons (for Docker services).[1] Another client, besides the CLI, is Docker Compose, for multi-container applications.[1]

Modern Docker Engine is not one monolith but a stack of cooperating components:

The responsibilities are split roughly as follows:

  • dockerd — the API server and object manager. It receives every request, holds the state of images/containers/networks/volumes, and orchestrates the rest of the stack.[1]
  • containerd — "an industry-standard container runtime", a daemon that "manages the complete container lifecycle of its host system, from image transfer and storage to container execution and supervision to low-level storage to network attachments and beyond".[9] Since Docker Engine 29, the containerd image store is the default for fresh installs.[12]
  • runc — the OCI runtime that actually creates and runs container processes, applying namespaces, cgroups and filesystem isolation; it is the reference implementation of the OCI runtime specification.[7]
  • containerd-shim — the small per-container process that keeps the container's stdio open and lets the daemons restart without killing containers.
  • The Linux kernel — the isolation primitives themselves: namespaces (each container gets its own set), cgroups for resource limits, OverlayFS for layered filesystems, seccomp and capabilities for security.[1]
  • A registry — stores and distributes images; Docker Hub is the default, and docker pull/docker run fetch from it while docker push uploads to it.[1]
  • BuildKit — the builder backend that executes image builds; since its adoption it is "the default builder for Docker Desktop and Docker Engine users".[13]

Docker in action

Two flows cover most of what Docker does: building an image from a Dockerfile, and running a container from an image.

Image build

To build your own image you write a Dockerfile, "a text document that contains all the commands a user could call on the command line to assemble an image".[14] Each instruction — FROM, RUN, COPY, CMD, ENTRYPOINT, ... — becomes a layer of the image.[1] Because layers are immutable and cached, "when you change the Dockerfile and rebuild the image, only those layers which have changed are rebuilt" — the main reason images are so lightweight and builds so fast compared with other virtualization technologies.[1]

Underneath the classic docker build command sits BuildKit. Its core is a Low-Level Build (LLB) format: "a content-addressable dependency graph" that lets it detect and skip unused build stages, run independent stages in parallel, and transfer only the changed files of the build context.[13] A "frontend" — itself distributed as an image — turns the human-readable Dockerfile into LLB, so the Dockerfile language can evolve without rebuilding the engine.[13]

Container run

A container is "a runnable instance of an image", "defined by its image as well as any configuration options you provide to it when you create or start it".[1] When you run docker run -i -t ubuntu /bin/bash, the documented sequence is:[1]

  1. If the ubuntu image is not present locally, Docker pulls it from the configured registry.
  2. Docker creates a new container (as with docker container create).
  3. Docker allocates a read-write filesystem to the container as its final layer — the only layer a running container may write to.
  4. Docker creates a network interface connecting the container to the default network and assigns it an IP address.
  5. Docker starts the container and executes /bin/bash, attached to your terminal.
  6. When you type exit, the container stops but is not removed — you can start it again or delete it.

That last point is important: "when a container is removed, any changes to its state that aren't stored in persistent storage disappear."[1] Data that must survive goes into volumes or bind mounts; ephemeral state simply dies with the container.

Key design decisions

Containers share the host kernel

The founding decision: a container is not a mini-VM. Docker uses Linux namespaces to give each container an isolated view (processes, networking, filesystem, users, and more), and cgroups to limit CPU, memory and I/O.[1] Because every container on a host shares the same kernel, there is no guest OS to boot, so containers start in seconds and many more fit on a machine than VMs would.[2]

The trade-off is architectural: isolation is as good as the kernel's — and the admin's — configuration. Docker historically defaulted to cgroups v1; the ecosystem has since moved to cgroups v2, and as of Docker Engine 29 cgroup v1 is deprecated but supported until at least May 2029.[12] On macOS and Windows, Docker Desktop bundles the daemon, the client, Docker Compose, Kubernetes and more, so the same Linux-based engine is available everywhere.[1]

Images are immutable, layered and content-addressed

An image is a "read-only template", often "based on other images, with some additional customization" — e.g. the Ubuntu image plus the Apache web server plus your application.[1][15].

As most consumer images are a combination of base images with customisation, they can be considered as the sum of multiple layers. Docker identifies them by a hash of their content, and identical layers are shared between images via content-addressable storage, which means they are only downloaded and stored once, regardless of the number of consumer images.[15]

  • Union filesystems — at run time, layers are stacked into a single unified view; the container's root is set to that view with chroot. On top of the read-only image layers, a directory is created for the running container, so the container writes without ever touching the image. For additional robustness, Docker uses copy-on-write. [15]

This design ensures that images are always state-consistent and optimised for network distribution and storage.

The Dockerfile is a declarative build recipe

Rather than mutating a running machine (and hoping the result is reproducible), Docker builds images from a declarative Dockerfile — "all the commands a user could call on the command line to assemble an image".[14] A valid Dockerfile must start with FROM, which "initializes a new build stage and sets the base image".[14] The key instructions:

  • RUN — "execute any commands to create a new layer on top of the current image".[14]
  • COPY / ADD — copy files from the build context (or another stage) into the image.
  • CMD / ENTRYPOINT — define the command executed when a container starts; ENTRYPOINT configures the container to run "as an executable", CMD provides default arguments.[14]
  • Multiple FROM lines enable multi-stage builds — build in one stage, copy only the artifacts into a minimal final stage.[14]

Because each instruction is a layer, the instruction sequence is also the cache key: RUN layers are reused on rebuild unless invalidated — a changed COPY or ADD invalidates the cache — so instructions placed early in the file are the ones that get reused across builds.[14] BuildKit hardened this model by tracking "the checksums of build graphs" instead of heuristics, making the cache faster, more precise and portable — it can even be exported to a registry.[13]

A daemon-centric client-server design

Docker uses a single privileged daemon that owns all state, fronted by a deliberately thin CLI.[1] Every docker command is a REST call to the Engine API; the daemon performs it and reports back.[1] This concentrates complexity in one place, enforcing the object model and facilitating security auditing. It also makes the engine highly scriptable — CI systems, orchestrators and tools like Compose and Docker Desktop all call the daemon's API.[1]

A logical downside of this complexity concentration is a long-lived server process as a single point of failure. To mitigate the risk, the daemon behaves a bit like a manager: it does not handle low-level container lifecycle itself but instead delegates them to containerd. This way, in case of state corruption the daemon can restart without killing containers, a bit like how a manager can go for a short refresher while ground workers continue to function normally.

The Engine API is versioned and stable

As Docker's contract with the outside world, the Docker API must be stable and compatible across versions. To achieve this, Docker's Engine API is versioned. Clients and daemons negotiate a mutually compatible API version, so a newer CLI can drive an older daemon and vice versa.

However, this unfortunately does not mean that all versions of Docker are mutually compatible. Docker Engine 29's daemon now requires API version v1.44 or later, and its client dropped support for negotiating versions below v1.44, as the changes have become genuinely hard to bridge.[12]

In a nutshell, Docker's versioned API does not guarantee that old code would work forever, but it provides additional years of runway to migrate.[12]

Bundled software, standardized primitives

As can be seen in the components and interactions diagram, Docker has a layered architecture.

An important factor leading to Docker's lasting success is its interoperability. Docker's low-level layers are built as open standards and neutral primitives: The Open Container Initiative (OCI) specifications for runtime, image, and distribution; containerd for runtime daemon;runc for runtime execution; and BuildKit for builder backend. As such a Docker image can be built, distributed and run by any OCI-compliant toolchain with no additional arguments".[7]

Try it yourself

The commands below are drawn from Docker's official documentation, as read on 2026-08-31.

# 1. Confirm the welcome-to-docker image exists on Docker Hub and view the number of times it has been pulled
curl -s "https://hub.docker.com/v2/repositories/docker/welcome-to-docker/" | grep -o '"pull_count":[0-9]*'

Output:

"pull_count":6104168
# 2. Start a web server container, publishing port 8080 on the host to port 80 in the container
docker run -d -p 8080:80 docker/welcome-to-docker

The output from this command is the full container ID. Visit http://localhost:8080 to see a small website served by this test container.

# 3. List running containers
docker ps

Output:

CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS          PORTS                      NAMES
a1f7a4bb3a27   docker/welcome-to-docker   "/docker-entrypoint.…"   11 seconds ago   Up 11 seconds   0.0.0.0:8080->80/tcp       gracious_keldysh
# 4. Stopping the container
docker stop a1f7a4bb3a27

Only enough of the ID to make it unique is needed. For instance, docker stop a1f would work as long as there is not another active container with an ID beginning with "a1f".[2]

# 5. Run an interactive Ubuntu shell in a container
docker run -i -t ubuntu /bin/bash

If the image isn't local, Docker pulls it; then it creates the container, allocates its writable layer, attaches it to the default network, starts it and runs /bin/bash — type exit and the container stops but is not removed.[1]

6. Build your own image from a Dockerfile

# 6a. make a dedicated directory and move into it.
#     That directory will be the build context — files in it (and only in it)
#     can be referenced by COPY / ADD instructions.
mkdir myapp && cd myapp


# 6b. Create the Dockerfile and the script to be copied into the image.
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl
COPY app.sh /app.sh
CMD ["/app.sh"]
EOF

cat > app.sh <<'EOF'
#!/bin/sh
echo "Hello from my Docker image!"
EOF
chmod +x app.sh

A Dockerfile is "a text file containing instructions for building source code", read top to bottom; the instruction syntax is defined by the Dockerfile reference.[16][14] Each instruction becomes a layer of the image.[1] The essential instructions:

  • FROM — "defines a base for your image"; a valid Dockerfile starts with it.[16][14]
  • RUN — "executes any commands in a new layer on top of the current image and commits the result" (here: install curl).[16]
  • COPY — copies files or directories from the build context into the image's filesystem.[16]
  • CMD — "defines the default program that is run once you start the container"; only the last CMD counts, and the JSON-array form used here runs /app.sh directly, without a shell.[16]

The Dockerfile must live in the root of the directory you pass to docker build. That directory then becomes the build context, or the set of files that your build can access.[17] By default the file is named Dockerfile, without a file extension, so that docker build needs no extra flags.

When there are alternative builds, a common convention is *.Dockerfile) pass * with -f / --file.[16]

To keep the build context lean, list what must not reach the builder — node_modules, build artifacts, secrets — in a .dockerignore file in the context root: the build client drops matching files before the context is sent.[17]

# 6c. Build the image 
docker build -t myapp:latest .

-t names and tags the image, the trailing dot selects the build context (the current directory).[16] Docker reads the instructions in order; each creates a layer, and unchanged layers are reused from the build cache on the next build.[14][1]

Output:

[+] Building 2.6s (7/7) FINISHED
 => [internal] load build definition from Dockerfile    0.0s
 => [internal] load .dockerignore                       0.0s
 => [internal] load build context                       0.0s
 => => transferring context: 2B                         0.0s
 => [1/4] FROM docker.io/library/ubuntu:24.04           1.8s
 => [2/4] RUN apt-get update && apt-get install -y curl  0.7s
 => [3/4] COPY app.sh /app.sh                           0.0s
 => [4/4] CMD ["/app.sh"]                               0.0s
 => exporting to image                                  0.0s
 => => naming to docker.io/library/myapp:latest         0.0s
# 6d. Run it: start a container from the image; --rm removes it on exit.
docker run --rm myapp:latest

Output:

Hello from my Docker image!
# 6e. Confirm the image is stored in the local image store.
docker image ls

Output:

REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
myapp         latest    a1b2c3d4e5f6   2 minutes ago    120MB

References

  1. ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). What is Docker? (Webpage). In Docker Docs (Website).
  2. ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). What is a container? (Webpage). In Docker Docs (Website).
  3. ↑ ↑ Hykes, S. (2017). Introducing Moby Project (Webpage). In Docker Blog (Website).
  4. ↑ Seroter, R. (n.d.). Introducing Docker, Inc: dotCloud Goes All-In On Container Technology (Webpage). In InfoQ: Software Development News, Trends & Best Practices - InfoQ (Website).
  5. ↑ Q1419. (2013). As Open Source Docker Grows DotCloud Changes Name, Business Model - Linux.com (Webpage). In Linux.com - News For Open Source Professionals (Website).
  6. ↑ Docker, Inc. (2014). It’s Here: Docker 1.0 (Webpage). In Docker Blog (Website).
  7. ↑ ↑ ↑ Open Container Initiative. (n.d.). About the Open Container Initiative (Webpage). In opencontainers.org (Website).
  8. ↑ Hykes, S. (2016). containerd – a core container runtime project for the industry (Webpage). In Docker Blog (Website).
  9. ↑ ↑ The Linux Foundation. (n.d.). containerd.io (Website).
  10. ↑ Docker, Inc. (n.d.). Moby Project (Website).
  11. ↑ Moby Project. (n.d.). GitHub - moby/moby: The Moby Project - a collaborative project for the container ecosystem to assemble container-based systems (Webpage). In GitHub · Change is constant. GitHub keeps you ahead. (Website).
  12. ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). Docker Engine version 29 release notes (Webpage). In Docker Docs (Website).
  13. ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). BuildKit (Webpage). In Docker Docs (Website).
  14. ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). Dockerfile reference (Webpage). In Docker Docs (Website).
  15. ↑ ↑ ↑ Docker, Inc. (n.d.). Understanding the image layers (Webpage). In Docker Docs (Website).
  16. ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (2026). Dockerfile overview (Webpage). In Docker Docs (Website).
  17. ↑ ↑ Docker, Inc. (2026). Build context (Webpage). In Docker Docs (Website).

Further reading