What is Docker? #
Docker is one of the most influential technologies in modern software engineering. Nearly every backend, DevOps, and cloud engineer has — or is — working with Docker on a daily basis. It’s the foundation for microservices, the basis for Kubernetes, and the de facto standard for packaging applications in the cloud-native era.
The question beginners usually ask isn’t “What is Docker?” — because the answer is easy to find. The more important question is: why did Docker appear, what problem does it solve, and how do its core concepts really work? Without that understanding, Docker just feels like a magic tool that conjures containers on your terminal screen. With it, Docker becomes a tool you can design, debug, and optimize to fit your needs.
This article is the foundation for everything discussed in the Basic section. After reading it, you’ll understand what containerization is, how it differs from traditional approaches, what Docker’s components are, and how Docker’s workflow runs from code on your laptop to an application running on a production server.
Containerization Before Docker #
To understand Docker, you first need to understand the problem it was built to solve. That problem goes by the names dependency hell and environment drift.
In the traditional approach, applications run directly on top of the server’s operating system. Libraries are installed globally, runtime versions are decided by the server admin, and environment configuration is scattered across many places. The result: an application that runs perfectly on a developer’s laptop often fails on staging, and fails even more often in production.
The legendary phrase born from this situation is:
“Works on my machine.”
Containerization is an approach to packaging an application together with all of its dependencies into a single, isolated, portable unit. That unit can run consistently anywhere — a developer’s laptop, an internal server, a cloud VM, or a Kubernetes cluster.
flowchart LR
A[Traditional Application] --> B[Server: OS + Global Libs]
C[Containerized Application] --> D[Container: App + Deps + Runtime]
B --> E[Dependency conflicts]
D --> F[Consistent anywhere]Containers aren’t a new concept. Unix has had chroot since 1979, and FreeBSD has had jails since 2000. But containerization only became practical and popular when Docker arrived in 2013, bringing together existing Linux technologies (cgroups, namespaces, union filesystems) into a tool that’s easy for developers to use.
Defining Docker #
Docker is an open-source platform for building, packaging, and running applications inside containers. Docker provides three main things:
- Build — a tool for creating application images from a recipe called a Dockerfile.
- Ship — a standard way to distribute images through a registry (Docker Hub, GHCR, ECR, etc.).
- Run — a runtime for turning images into isolated containers on top of a host.
In one concise sentence:
Docker = a standard way to package an application + its dependencies into a portable image, then run it as a container anywhere.
What makes Docker revolutionary isn’t the container technology itself (that already existed), but the developer experience. Before Docker, creating a container meant compiling kernel patches, writing manual namespace scripts, and understanding Linux internals. With Docker, you just type docker run nginx and the container is up in seconds.
A Brief History of Docker #
Docker was first introduced in 2013 at PyCon US by Solomon Hykes, then CTO of dotCloud (a PaaS company). Docker 0.1.0 launched in March 2013, and its growth has been exponential ever since.
The backstory is simple: dotCloud had an internal tool for packaging and running applications on their PaaS infrastructure. It worked extremely well for the internal team but was never extended outward. Solomon and the team decided to open-source that technology — that became Docker.
timeline
1979 : chroot - first filesystem isolation in Unix
2000 : FreeBSD Jails - jail concept for processes
2008 : LXC - Linux Containers, combining cgroups + namespaces
2013 : Docker 0.1.0 - released at PyCon US
2014 : Docker 1.0 - stable, industry adoption begins
2015 : OCI formed - open container standard
2016 : Kubernetes 1.4 + Docker - orchestration goes mainstream
2020 : Docker Compose v2, BuildKit - modern toolchain
2023 : Docker 25+ - security focus, rootless, multi-archDocker isn’t alone. In 2015, the Open Container Initiative (OCI) was founded under the Linux Foundation to standardize container formats — the image spec and the runtime spec. As a result, images built with Docker can be run by containerd, CRI-O, Podman, and any other OCI-compliant runtime.
This is important to understand: Docker is a product (a tool with a CLI and daemon), not a standard. The standard is OCI. Docker is just the most popular implementation.
Historical notes:
- The Docker logo (moby whale + container) was inspired by the concept of “container shipping” in logistics. The metaphor is the same: a Docker container packages goods in a standard format that can be carried by any ship (server).
- “Moby” is the name of Docker’s mascot (the whale). Several Docker sub-projects use this name, for example the Moby Project for its open-source components.
How Docker Works at the System Level #
Before diving into the components, understand the big picture first. Docker works in three main stages.
flowchart LR
A[Dockerfile] -->|docker build| B[Docker Image]
B -->|docker push| C[Registry]
C -->|docker pull| D[Docker Host]
B -->|docker run| D
D --> E[Container]
E --> F[App running]- Build — The developer writes a Dockerfile, then runs
docker build. Docker produces an image that is read-only and layered. - Ship — The image is pushed to a registry (Docker Hub, GHCR, ECR, etc.) so it can be shared with the team or other environments.
- Run — On any host with Docker Engine, the image is pulled and run with
docker run. The image becomes a container — a process running on top of the host kernel with namespace and cgroups isolation.
What you need to underline: image and container are two different things. An image is a blueprint (a file). A container is a runtime process (an instance) created from the image.
Docker’s Core Components #
Docker consists of five main components you need to understand. Once you understand all of them, every Docker command starts to make sense.
Image #
An image is a read-only template containing everything needed to run an application: base OS, runtime, libraries, code, and default configuration.
Image characteristics:
- Read-only — an image can’t be modified once created. Changes are stored in a separate container layer.
- Layered — an image is made up of cached layers. Each Dockerfile instruction produces one layer. This is what makes Docker builds fast.
- Reusable — one image can serve as the base for another image (multi-stage builds).
Think of an image as a snapshot of the application at one point in time. A running application isn’t an image — that’s a container.
flowchart TB
subgraph Image["Docker Image"]
L1[Layer 1: Base OS]
L2[Layer 2: Runtime]
L3[Layer 3: Library]
L4[Layer 4: App Code]
end
L1 --> L2 --> L3 --> L4Container #
A container is a runtime instance of an image. It’s a Linux process running with its own namespaces and cgroups, isolated from the host and from other containers.
Container characteristics:
- Lightweight — shares the kernel with the host, doesn’t carry its own OS.
- Isolated — has its own filesystem, network, PID, and user namespaces.
- Ephemeral — ideally disposable. Persistent data should live in volumes.
- Immutable — runtime changes are written to a separate layer, not into the image.
One container should ideally run one main process. For example, one container for the web server, one for the database, one for the worker. This principle is known as single concern or one process per container.
Dockerfile #
A Dockerfile is a text file containing instructions for building an image. It’s the image’s source of truth — anyone can reproduce the exact same image from the same Dockerfile.
A minimal Dockerfile example:
FROM golang:1.22-alpine
WORKDIR /app
COPY . .
RUN go build -o server
CMD ["./server"]
Each line (FROM, WORKDIR, COPY, RUN, CMD) produces one layer. Line order determines cache hits — lines that change frequently should be placed at the bottom.
// ANTI-PATTERN: COPY the entire build context up front; the cache is invalidated on every code change
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
// CORRECT: COPY the dependency definition first so npm install can be cached
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
This principle is covered in depth in the Dockerfile section, but the gist is: the order of Dockerfile instructions determines build speed.
Docker Engine #
Docker Engine is the runtime that runs containers on a host. It consists of three parts:
- Docker Daemon (
dockerd) — the background process that manages images, containers, networks, and volumes. - REST API — the interface the Docker CLI uses to communicate with the daemon.
- Docker CLI (
docker) — the command-line tool you type into the terminal.
flowchart LR
User -->|docker ...| CLI[Docker CLI]
CLI -->|REST API| Daemon[Docker Daemon]
Daemon -->|manage| Containers[(Containers)]
Daemon -->|manage| Images[(Images)]
Daemon -->|manage| Networks[(Networks)]
Daemon -->|manage| Volumes[(Volumes)]Today, Docker Engine also bundles containerd and runc. Containerd is the high-level runtime that manages the container lifecycle; runc is the low-level runtime that actually creates the container process according to the OCI standard.
Docker Registry #
A registry is a place to store and distribute images. Registries can be public (anyone can access) or private (restricted to a team/organization).
Popular registries:
| Registry | Provider | Best for |
|---|---|---|
| Docker Hub | Docker Inc. | Public images, popular base images |
| GitHub Container Registry | GitHub | Direct integration with GitHub repos |
| GitLab Container Registry | GitLab | Self-hosted GitLab |
| Amazon ECR | AWS | AWS ecosystem, integrated IAM |
| Google Artifact Registry | GCP | GCP ecosystem |
| Azure Container Registry | Azure | Azure ecosystem |
Registry workflow:
sequenceDiagram
participant Dev as Developer
participant Reg as Registry
participant CI as CI Server
participant Prod as Production
Dev->>Dev: docker build -t app:1.0
Dev->>Reg: docker push app:1.0
CI->>Reg: docker pull app:1.0
CI->>CI: run tests
CI->>Reg: docker push app:1.1
Prod->>Reg: docker pull app:1.1
Prod->>Prod: docker run app:1.1The exact same image tested in CI gets deployed to production. No more “but in production we used a different library”. This is what’s called an immutable artifact — the image is a single contract between developer, CI, and production.
Docker Workflow End-to-End #
To see how all the components work together, look at the complete workflow below.
flowchart TD
A[Developer writes code] --> B[Write/Update Dockerfile]
B --> C[docker build]
C --> D{Tests pass?}
D -- No --> E[Fix code]
E --> B
D -- Yes --> F[docker push to Registry]
F --> G[CI/CD pipeline trigger]
G --> H[Deploy to Staging]
H --> I{Staging OK?}
I -- No --> E
I -- Yes --> J[Deploy to Production]
J --> K[Container running on server/cloud]In practice, you don’t run these steps manually. Everything is automated by a CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins, etc.). The developer just pushes code to Git, and the pipeline takes over: build the image, run tests, push to the registry, deploy.
Common mistake: Many teams run Docker on their laptops but still deploy manually via SSH to the server. That’s a half-measure. Docker without CI/CD automation only solves the “environment consistency” problem, not the “deployment speed and reliability” problem. For Docker’s full value, deployment automation is a must, not an option.
Why Docker Is So Influential #
Docker isn’t just a tool — it changed how the software industry works. Several factors made Docker the de facto standard.
Total consistency. The same image runs on laptops, staging, and production. No more differences in host OS, library versions, or local configuration slipping through.
Fast onboarding. New developers can run the entire application stack with a single command (docker compose up) without installing dependencies one by one. All they need is Docker.
Efficient resources. Containers are far lighter than VMs. A single host can run dozens of containers without the overhead of guest OSes.
A mature ecosystem. Docker doesn’t stand alone — it’s part of a large ecosystem: Docker Compose for multi-container, Kubernetes for orchestration, Helm for templating, registries for distribution, and more.
An industry standard. Almost every cloud provider (AWS, GCP, Azure) offers container services compatible with Docker OCI images. Modern programming languages, frameworks, and tools are already container-first.
mindmap
root((Docker))
Build
Dockerfile
BuildKit
Multi-stage
Ship
Docker Hub
GHCR
ECR
Run
Docker Engine
containerd
Podman alt
Orchestrate
Compose
Swarm
Kubernetes
Monitor
cAdvisor
Prometheus
GrafanaDocker vs Traditional Approaches #
To clarify Docker’s position, compare it with the two common approaches that came before it: bare metal and virtual machines.
| Aspect | Bare Metal | Virtual Machine | Docker Container |
|---|---|---|---|
| Isolation | None | OS-level (hardware) | Process-level (kernel) |
| Boot time | Minutes | Minutes | Milliseconds–seconds |
| Image size | No image | GB | MB |
| Memory overhead | Minimal | Large (1–4 GB/VM) | Very small (MB) |
| Density per host | Low | Low (5–20 VMs) | High (50–500 containers) |
| Portability | Very low | Medium | Very high |
| Provisioning time | Hours–days | Minutes | Seconds |
| Best for | Specialized hardware | Multi-OS workloads | Microservices, cloud-native |
Docker is not a replacement for VMs in every situation. VMs still win for:
- Running different OSes on one host (Windows on a Linux host, for example).
- Workloads requiring very strict security isolation (regulated environments, multi-tenancy).
- Legacy applications incompatible with the host kernel.
Containers win in almost every modern workload: web services, APIs, workers, job processors, microservices, batch processing. That’s why VM + Container is the most common pattern in production — the VM is the infrastructure boundary, the container is the application unit.
The Ecosystem Around Docker #
Docker is the entry point, not the end goal. Once you understand Docker, you’ll find other tools that work on top of or alongside it.
| Tool | Role | When to use |
|---|---|---|
| Docker Compose | Multi-container on one host | Local development, small deployments |
| Docker Swarm | Docker’s built-in orchestrator | Small–medium clusters, rarely used in industry |
| Kubernetes | Industry-standard orchestrator | Large-scale production, multi-service |
| BuildKit | Modern build engine | Complex Dockerfiles, multi-stage, optimal caching |
| Podman | Daemonless Docker alternative | Environments requiring rootless |
| containerd | Standard container runtime | Used by Kubernetes, not a direct CLI |
| CRI-O | Lightweight runtime for Kubernetes | containerd alternative in K8s |
For beginners: Don’t try to learn everything at once. Understand Docker first, then Docker Compose, and only start looking at Kubernetes when you genuinely need multi-host orchestration. This order reflects a healthy learning curve.
Terms You Need to Know #
Before moving on to the next article, make sure you’re familiar with these terms. They’ll come up throughout the entire Docker section.
- Image — a read-only template containing the application + its dependencies.
- Container — a runtime instance of an image.
- Dockerfile — the recipe for building an image.
- Layer — a part of the image produced by each Dockerfile instruction.
- Registry — a place to store images.
- Tag — an image version marker (e.g.
nginx:1.25). - Volume — a place to store persistent data outside the container.
- Network — the mechanism for communication between containers and the host.
- Docker Compose — a tool for defining and running multi-container setups.
- Orchestrator — a tool for managing many containers across many hosts (Kubernetes, Swarm).
Summary #
- Docker is an open-source platform for building, packaging, and running applications inside containers. It appeared in 2013 and became the industry standard for containerization.
- Containers are isolated units at the OS process level, not hardware emulation like VMs. They’re lightweight, portable, and consistent.
- Image is a read-only blueprint; Container is a runtime instance of an image. This difference is fundamental and often misunderstood by beginners.
- Dockerfile is the source of truth for an image. Instruction order determines build speed because Docker uses layer caching.
- Docker Engine = daemon + CLI + API. It isn’t the standard — the standard is OCI (Open Container Initiative).
- Registry is image distribution: Docker Hub, GHCR, ECR, etc. The same image tested in CI must be the same one deployed to production.
- Docker ≠ VM. VMs isolate at the hardware level with a guest OS. Containers isolate at the kernel level with namespaces + cgroups. The two complement each other, they don’t replace each other.
- The Docker ecosystem is vast: Compose, BuildKit, containerd, Kubernetes, etc. Understand Docker first, then climb to the tools above it.
Next: Traditional Deployment →