HowItWorks:Docker: Difference between revisions
AI-assisted (RonzzWikiCowriter): complete the "Bundled software, standardized primitives" section (OCI, containerd/CNCF, runc, BuildKit) (via update-page on MediaWiki MCP Server) |
AI-assisted (RonzzWikiCowriter): expand step 6 (build your own image from a Dockerfile: where to put it, what's in it, how to build) (via update-page on MediaWiki MCP Server) |
||
| Line 309: | Line 309: | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
# 6. Build your own image from a Dockerfile | # 6. Build your own image from a Dockerfile | ||
# Dockerfile | # Where to put the Dockerfile, what's in it, then how to build. | ||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Where to put it.''' The Dockerfile lives in the root of the directory you pass to <code>docker build</code> — that directory is the '''build context''', "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; for differently-named files (a common convention is <code>*.Dockerfile</code>) pass the name with <code>-f</code> / <code>--file</code>.<ref>{{#cite:Q1437}}</ref> | |||
<syntaxhighlight lang="bash" copy> | |||
# 6a. Where: 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 | |||
</syntaxhighlight> | |||
'''What's in it.''' A Dockerfile is "a text file containing instructions for building your 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> | |||
<syntaxhighlight lang="bash" copy> | |||
# 6b. What: create the Dockerfile and the script it copies 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> | |||
'''How to build.''' <code>docker build -t myapp:latest .</code> — <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> | |||
<syntaxhighlight lang="bash" copy> | |||
# 6c. How: build the image, then list it locally. | |||
docker build -t myapp:latest . | |||
</syntaxhighlight> | |||
Documented output (step numbers, hashes and timings vary):<ref>{{#cite:Q1436}}</ref> | |||
<syntaxhighlight lang="text" copy> | |||
[+] 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 | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="bash" copy> | |||
# 6d. Run it: start a container from the image; --rm removes it on exit. | |||
docker run --rm myapp:latest | |||
</syntaxhighlight> | |||
Documented output: | |||
<syntaxhighlight lang="text" copy> | |||
Hello from my Docker image! | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="bash" copy> | |||
# 6e. Confirm the image is stored in the local image store. | |||
docker image ls | |||
</syntaxhighlight> | |||
Documented output (image ID, age and size vary):<ref>{{#cite:Q1437}}</ref> | |||
<syntaxhighlight lang="text" copy> | |||
REPOSITORY TAG IMAGE ID CREATED SIZE | |||
myapp latest a1b2c3d4e5f6 2 minutes ago 120MB | |||
</syntaxhighlight> | |||
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> | |||
== References == | == References == | ||
Revision as of 17:17, 31 August 2026
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 runfetch from it whiledocker pushuploads 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]
- If the
ubuntuimage is not present locally, Docker pulls it from the configured registry. - Docker creates a new container (as with
docker container create). - Docker allocates a read-write filesystem to the container as its final layer — the only layer a running container may write to.
- Docker creates a network interface connecting the container to the default network and assigns it an IP address.
- Docker starts the container and executes
/bin/bash, attached to your terminal. - 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;ENTRYPOINTconfigures the container to run "as an executable",CMDprovides default arguments.[14]- Multiple
FROMlines 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 decision leading to Docker's lasting success is that its low-level layers are built as open standards and neutral projects:
- The Open Container Initiative (OCI) — "a lightweight, open governance structure (project), formed under the auspices of the Linux Foundation, for the express purpose of creating open industry standards around container formats and runtimes".[7] Launched on June 22, 2015 by Docker, CoreOS and other container industry leaders, it received Docker's container format and runtime, runc, as "the cornerstone of this new effort".[7] The OCI maintains three specifications:[7]
- runtime-spec — how to run a "filesystem bundle" that is unpacked on disk;
- image-spec — how to create an OCI image: "an image manifest, a filesystem (layer) serialization, and an image configuration";
- distribution-spec — the API to distribute container images, which reached v1.0 in May 2020.
- containerd — the container runtime daemon spun out of Docker in 2016[8] and accepted into the Cloud Native Computing Foundation (CNCF) in March 2017; in February 2019 it became the CNCF's fifth project to graduate, after Kubernetes, Prometheus, Envoy and CoreDNS, with "its widest usage and adoption as the layer between the Docker engine and the OCI runc executor".[16]
- runc — the OCI runtime donated by Docker: a standalone command-line tool that spawns and runs containers according to the OCI specification, the reference implementation of the runtime-spec.[7]
- BuildKit — "the builder backend used by Docker", itself an independent open-source project developed in the Moby ecosystem (github.com/moby/buildkit) rather than as a closed part of the engine.[13]
The payoff is interoperability: the primitives are not Docker-specific. Because image formats, runtimes and registries all follow OCI standards, the same image can be built, distributed and run by any OCI-compliant toolchain, and the workflow is defined so that it "should support the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image 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 really exists on Docker Hub (live check, 2026-08-31)
curl -s "https://hub.docker.com/v2/repositories/docker/welcome-to-docker/" | grep -o '"pull_count":[0-9]*'
Output (verified live):
"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."[2] The container now serves a small website at http://localhost:8080.
# 3. List running containers
docker ps
Documented output (IDs and names vary):[2]
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. Stop the container
docker stop a1f7a4bb3a27
"Only enough of the ID to make it unique" is needed — docker stop a1f would do.[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
# Where to put the Dockerfile, what's in it, then how to build.
Where to put it. The Dockerfile lives in the root of the directory you pass to docker build — that directory is the build context, "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; for differently-named files (a common convention is *.Dockerfile) pass the name with -f / --file.[18]
# 6a. Where: 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
What's in it. A Dockerfile is "a text file containing instructions for building your source code", read top to bottom; the instruction syntax is defined by the Dockerfile reference.[18][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.[18][14]RUN— "executes any commands in a new layer on top of the current image and commits the result" (here: installcurl).[18]COPY— copies files or directories from the build context into the image's filesystem.[18]CMD— "defines the default program that is run once you start the container"; only the lastCMDcounts, and the JSON-array form used here runs/app.shdirectly, without a shell.[18]
# 6b. What: create the Dockerfile and the script it copies 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
How to build. docker build -t myapp:latest . — -t names and tags the image, the trailing dot selects the build context (the current directory).[18] 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]
# 6c. How: build the image, then list it locally.
docker build -t myapp:latest .
Documented output (step numbers, hashes and timings vary):[17]
[+] 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
Documented output:
Hello from my Docker image!
# 6e. Confirm the image is stored in the local image store.
docker image ls
Documented output (image ID, age and size vary):[18]
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest a1b2c3d4e5f6 2 minutes ago 120MB
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]
References
- ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). What is Docker? (Webpage). In Docker Docs (Website).
- ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). What is a container? (Webpage). In Docker Docs (Website).
- ↑ ↑ Hykes, S. (2017). Introducing Moby Project (Webpage). In Docker Blog (Website).
- ↑ 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).
- ↑ Q1419. (2013). As Open Source Docker Grows DotCloud Changes Name, Business Model - Linux.com (Webpage). In Linux.com - News For Open Source Professionals (Website).
- ↑ Docker, Inc. (2014). It’s Here: Docker 1.0 (Webpage). In Docker Blog (Website).
- ↑ ↑ ↑ ↑ ↑ ↑ ↑ Open Container Initiative. (n.d.). About the Open Container Initiative (Webpage). In opencontainers.org (Website).
- ↑ ↑ Hykes, S. (2016). containerd – a core container runtime project for the industry (Webpage). In Docker Blog (Website).
- ↑ ↑ The Linux Foundation. (n.d.). containerd.io (Website).
- ↑ Docker, Inc. (n.d.). Moby Project (Website).
- ↑ 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).
- ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). Docker Engine version 29 release notes (Webpage). In Docker Docs (Website).
- ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). BuildKit (Webpage). In Docker Docs (Website).
- ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (n.d.). Dockerfile reference (Webpage). In Docker Docs (Website).
- ↑ ↑ ↑ Docker, Inc. (n.d.). Understanding the image layers (Webpage). In Docker Docs (Website).
- ↑ Cloud Native Computing Foundation. (2019). Cloud Native Computing Foundation announces containerd graduation (Webpage). In cncf.io (Website).
- ↑ ↑ ↑ Docker, Inc. (2026). Build context (Webpage). In Docker Docs (Website).
- ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Docker, Inc. (2026). Dockerfile overview (Webpage). In Docker Docs (Website).
Further reading
- What is Docker? — the official overview
- Docker Engine — engine documentation
- Dockerfile reference — the build language
- BuildKit — the builder backend
- Docker Compose — multi-container applications
- containerd — the container runtime
- Open Container Initiative — the container standards
- Moby Project — the open framework behind Docker
- moby/moby — the engine's source code