When we work with Docker, we usually run commands like docker run, docker build or docker compose up.
But have you ever wondered what actually happens after you type docker run nginx in the terminal?
How does Docker download an image? Who creates the container? Who starts the container's process? And what is the real difference between Docker Engine, containerd and runc?
Today we will try to understand Docker's whole architecture in simple terms.
What is Docker, really?
Many people think Docker simply means containers. But Docker is not just a tool for running containers.
Docker is a complete containerization platform that lets you build, package, distribute, run and manage applications.
Several important components work together in the Docker ecosystem.
Docker Platform
│
├── Docker CLI
│
├── Docker API
│
├── Docker Engine (dockerd)
│
├── Build Tools (BuildKit / Buildx)
│
├── Images & Registries
│
├── Networking
│
├── Volumes
│
├── Authentication & Authorization
│
├── Security
│
└── Container Runtime
│
├── containerd
│
└── runc
Each of these components has its own responsibility.
From building an application's Docker image to running it as a container, different components each do their part.
Let's go through them one by one.
1. Docker CLI — How we talk to Docker
The Docker CLI (Command Line Interface) is the tool we use to give Docker instructions from the terminal.
For example:
docker run nginx
docker ps
docker build -t my-app .
docker compose up
docker logs container-name
Most of us use these all the time.
The interesting part is that the Docker CLI does not run containers itself.
The Docker CLI takes our command and sends a request to Docker Engine's API.
Say you run the following command:
docker run nginx
What does the Docker CLI do now?
It sends a request to the Docker API.
In plain words, the request says:
Create a new container from the nginx image and start it.
Docker Engine then processes that request.
The Docker CLI workflow
User
│
│ docker run nginx
▼
Docker CLI
│
│ API Request
▼
Docker Engine
│
▼
Container Creation
In other words, the CLI is the interface for using Docker, not the container runtime.
Real-world example:
When you click a button on a website, the button itself does not update the database. It sends a request to the backend API.
The Docker CLI works in much the same way.
2. Docker API — Docker's communication layer
The Docker CLI can talk to Docker Engine thanks to the Docker API.
Docker Engine exposes an API that different tools can use to access Docker's functionality.
Not only the Docker CLI: other applications can use the Docker API too.
For example:
Docker CLI ──────────┐
│
Portainer ───────────┤
│
CI/CD Tools ─────────┼──► Docker API
│ │
Custom Scripts ──────┤ ▼
│ Docker Engine
│
Automation Tools ────┘
Portainer here is a graphical management tool for managing a Docker environment.
You can start or stop containers from Portainer's web interface because it can talk to the Docker API.
In the same way, GitLab CI, a self-hosted GitHub Actions runner or custom automation scripts can use the Docker API when they are configured to.
What can you do with the Docker API?
- Create new containers
- Start and stop containers
- Pull Docker images
- Build Docker images
- List running containers
- View a container's logs
- Inspect Docker networks
- Manage Docker volumes
What is the Docker socket?
On Linux, Docker Engine usually exposes a Unix socket.
/var/run/docker.sock
The Docker CLI uses this socket to talk to Docker Engine locally.
For example:
docker ps
When you run this command, the Docker CLI asks Docker Engine for information about running containers through the local Docker socket.
Docker socket security
There is an important security point here.
In a typical rootful Docker setup, access to the Docker socket means very powerful control over the host machine.
That is because the Docker API can be used to create privileged containers, mount host directories or access sensitive files.
So in production, you should not give just any container or user access to the Docker socket.
Be especially careful with configuration like this:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Mounting the Docker socket like this can make it possible to control the host's Docker Engine from inside that container.
3. Docker build tools — How is a Docker image made?
Before we can run a container, we need a Docker image.
Docker's build tools create that image.
The two key technologies in modern Docker image building are:
BuildKit: the image build execution engine.
Buildx: a plugin that gives the Docker CLI advanced build features.
Say you have a Node.js application.
You write a Dockerfile for it:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "start"]
Now, to build the image:
docker build -t my-node-app .
What happens when you run this command?
Dockerfile
│
▼
Docker Build
│
▼
BuildKit
│
├── Base Image Resolve
│
├── Build Instructions Process
│
├── Dependencies Install
│
├── Build Cache Use
│
└── Image Layers Create
│
▼
Final Docker Image
BuildKit runs the build process following the instructions in the Dockerfile.
Key features of the build tools:
- Processing Dockerfile instructions
- Downloading base images
- Build caching
- Creating image layers
- Multi-stage builds
- Build secrets support
- Multi-platform builds
- Optimizing the build process
Multi-stage Docker builds
In production, the dependencies you need to build an application are often not needed at runtime.
A multi-stage Dockerfile is very useful here.
Say you have a frontend application:
# Stage 1: Build Application
FROM node:22 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production Runtime
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
The first stage builds the application with Node.js.
The second stage copies only the generated static files into the Nginx image.
So the final image does not need to carry the Node.js build dependencies.
Benefit: the image is smaller, there are fewer unnecessary build dependencies, and production deployment is simpler.
4. Docker volumes — How does data survive when a container is deleted?
Docker containers are generally designed to be replaceable.
Say you are running a PostgreSQL database inside a container.
What happens to the database's data when you remove the container?
If the data lives only in the container's writable filesystem, it can be lost when the container is deleted.
The solution to this problem is Docker volumes.
A Docker volume keeps persistent data separate from the container's lifecycle.
Example: a PostgreSQL database
docker run -d \
--name postgres-db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql \
postgres:18
Here:
pgdata
is a Docker named volume.
And:
/var/lib/postgresql
is the data storage location of the PostgreSQL 18 image.
Note: the recommended data directory can change between PostgreSQL image versions. For older PostgreSQL images, /var/lib/postgresql/data is the usual path.
How does a volume work?
Host Machine
│
├── Docker Engine
│ │
│ └── PostgreSQL Container
│ │
│ └── Database Files
│ │
│ ▼
└── Docker Volume: pgdata
│
└── Persistent Storage
A named volume usually remains even after the container is removed.
So if you mount the same volume into a new, compatible PostgreSQL container, the earlier data is still available.
Of course, you still need to think about database version compatibility and a proper backup strategy.
Named volume vs bind mount
The two common approaches to persistent storage in Docker are:
| Feature | Named Volume | Bind Mount |
|---|---|---|
| Managed by | Docker | Host filesystem |
| Storage path | Docker manages | User specifies |
| Common use | Database, persistent data | Development files, configuration |
| Portability | Docker-managed storage | Depends on the host path |
Named volume example
docker run \
-v pgdata:/var/lib/postgresql \
postgres:18
Bind mount example
docker run \
-v /home/user/app:/app \
node:22
A bind mount connects a directory on the host machine directly to a directory in the container.
This is very useful in development, because it makes your local source code available inside the container.
Where are volumes used?
Volumes are used for database storage, uploaded files, persistent application state and some log storage scenarios.
But in production, having a volume does not by itself mean the data is safe. You need a separate backup and recovery process.
5. Docker authentication & authorization — Who can access what?
Docker authentication comes up most often when using a Docker registry.
A Docker registry is where Docker images are stored and distributed.
For example:
- Docker Hub
- GitHub Container Registry (GHCR)
- GitLab Container Registry
- Private Enterprise Registry
Public images can usually be pulled without logging in.
But accessing private images may require credentials.
Docker login
docker login
To log in to a specific registry:
docker login ghcr.io
An example of pulling a private image:
docker pull ghcr.io/company/private-app:latest
To push an image:
docker push myrepo/my-app:latest
The image name, credentials and registry permissions all have to be correct.
Authentication vs authorization
These are not the same thing.
Authentication: who are you?
Authorization: what are you allowed to do?
For example:
Developer
│
▼
Registry Login
│
▼
Authentication
│
▼
Permission Check
│
├── Pull Image: Allowed
│
└── Push Image: Denied
Here, a developer may be able to log in to the registry but still not have permission to push images.
Why does auth matter in production?
Enterprise environments typically use private container registries, CI/CD service accounts, credential helpers and scoped access tokens.
Credentials should never be hardcoded in a Dockerfile.
In CI/CD environments, it is best to use secrets management and least-privilege access.
One more thing to remember: registry authentication and local access control to the Docker daemon are two separate things.
6. Docker security — How isolated are containers?
Docker containers do not run a completely separate operating system the way virtual machines do.
Linux containers usually share the host machine's Linux kernel.
That is why container isolation and security matter so much.
Docker uses a range of Linux kernel security features.
| Security Feature | What it does |
|---|---|
| Namespaces | Isolates processes, network, mounts, users and so on |
| cgroups | Controls CPU, memory and resource usage |
| Linux Capabilities | Limits privileged operations |
| Seccomp | Filters system calls |
| AppArmor | Enforces security profiles |
| SELinux | Mandatory access control policies |
| User Namespaces | Maps container user IDs separately from the host's IDs |
| Read-only Filesystem | Limits write access to the container filesystem |
Let's look at these a little more simply.
Namespaces
Namespaces help create an isolated environment for a container.
Say two containers are running on the same Linux server.
Linux Host
│
├── Container A
│ ├── Process Namespace
│ ├── Network Namespace
│ └── Mount Namespace
│
└── Container B
├── Process Namespace
├── Network Namespace
└── Mount Namespace
The processes and network environment inside Container A can be kept separate from Container B.
cgroups
With cgroups, you can control how much CPU or memory a container can use.
Memory limit
docker run --memory=512m nginx
Here the container's memory limit is 512 MB.
CPU limit
docker run --cpus=1 nginx
Here the container is configured to use at most roughly one CPU core's worth of CPU time.
These limits matter for resource management in shared server environments.
Read-only filesystem
docker run --read-only nginx
This option makes the container's root filesystem read-only.
But if the application needs to write files at runtime, you have to configure separate writable mounts or tmpfs. Even the default Nginx configuration can run into problems with this option unless extra writable paths are provided.
Non-root user
Running the container's process as a non-root user is good security practice.
docker run --user 1000:1000 my-app
The application's file permissions and runtime requirements have to be compatible with it, though.
Privileged containers — Why are they risky?
docker run --privileged ubuntu
--privileged can give a container many extra host capabilities and device access.
That weakens container isolation.
So this option should not be used unless there is a specific need.
The core idea of production security
Give a container only as much permission as it needs.
This is called the Principle of Least Privilege.
7. containerd — The container lifecycle manager
Now we come to an important part of Docker's architecture.
When we run:
docker run nginx
Docker Engine does not do all of the work of creating the Linux process directly.
Docker Engine uses a container runtime layer underneath it.
The key component of that layer is containerd.
containerd is an industry-standard container runtime that manages the container lifecycle and related runtime operations.
Its responsibilities include:
- Managing container images
- Managing image content and snapshots
- Container lifecycle operations
- Managing runtime tasks
- Integrating with the lower-level OCI runtime
How Docker and containerd relate
Docker CLI
│
▼
Docker API
│
▼
Docker Engine (dockerd)
│
▼
containerd
│
▼
containerd-shim
│
▼
runc
│
▼
Container Process
Here, containerd-shim is an important runtime component that plays a role in the container process's lifecycle and I/O management.
containerd in Kubernetes
containerd is not only for Docker.
It is also a very commonly used container runtime in Kubernetes.
In modern Kubernetes architecture, Kubernetes usually talks to compatible runtimes through the CRI (Container Runtime Interface).
A simplified flow:
Kubernetes API Server
│
▼
Kubelet
│
▼
CRI
│
▼
containerd
│
▼
containerd-shim
│
▼
runc
│
▼
Linux Container
One important point: the built-in dockershim was removed in Kubernetes v1.24.
So a Kubernetes node can use containerd directly as its container runtime, without Docker Engine.
Docker and containerd are not competitors. Docker is a broader container platform, and containerd is a container runtime component.
8. runc — The component that creates the actual container process
At an even lower layer of Docker's architecture sits runc.
runc is a low-level, OCI-compatible container runtime.
OCI stands for: Open Container Initiative.
OCI standards help ensure interoperability of container images and runtime behavior.
runc's main job is to create and start the container process using the Linux kernel's capabilities.
Its work includes:
- Configuring Linux namespaces
- Applying cgroups configuration
- Setting up the container filesystem
- Applying security settings
- Starting the container's initial process
How does runc work?
Say you have run the following command:
docker run nginx
The simplified flow is then:
docker run nginx
│
▼
Docker CLI
│
▼
Docker API
│
▼
Docker Engine
│
▼
containerd
│
▼
containerd-shim
│
▼
runc
│
▼
Linux Kernel Features
│
▼
Nginx Container Process
Here, runc creates the container process according to the OCI runtime configuration.
After the container starts, runc usually does not stay around as a long-running process. containerd-shim takes on the role of supervising the running container's lifecycle.
This distinction matters, because containerd and runc do not do the same job.
containerd: higher-level runtime lifecycle management.
runc: low-level container process creation.
9. Docker's full architecture — The complete picture
Now let's try to understand all the components together.
DEVELOPER
│
▼
Docker CLI
│
▼
Docker API
│
▼
Docker Engine
(dockerd)
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
BuildKit / Networking Volumes /
Buildx Management Storage
│ │ │
└──────────────┼──────────────┘
│
▼
containerd
│
▼
containerd-shim
│
▼
runc
│
▼
Linux Kernel
│
▼
Running Container
This is a simplified, conceptual architecture.
In reality, image building, registry access, networking, storage and runtime operations do not all happen in one sequential pipeline. Docker Engine coordinates several subsystems.
10. A real example — What happens inside docker run nginx?
Now let's walk through a complete example.
Say you type this in the terminal:
docker run -d -p 8080:80 nginx
Running this one command sets off many operations inside Docker.
Step 1: The Docker CLI receives the command
The CLI parses the command's parameters.
Here:
-dmeans detached mode-p 8080:80means forwarding traffic from host port 8080 to container port 80nginxis the image name
Step 2: A request goes to the Docker API
The Docker CLI sends a request to Docker Engine's API to create a container.
Step 3: Docker Engine checks for the image
Docker Engine checks whether the Nginx image is available locally.
If it is not, it arranges to pull it from the registry.
Step 4: Registry authentication may be needed
The public Nginx image can usually be pulled anonymously.
For an image in a private registry, the appropriate credentials are needed.
Step 5: The container configuration is prepared
Docker Engine prepares the configuration for the container.
For example:
- Container filesystem
- Networking
- Port publishing
- Environment variables
- Volume mounts
- Security configuration
- Resource limits
Step 6: containerd manages the container task
Docker Engine talks to containerd to coordinate the runtime operations.
Step 7: runc creates the container process
runc is invoked through containerd-shim.
runc uses the Linux kernel's namespaces, cgroups and other isolation features to create and start the container process.
Step 8: The Nginx process starts
Nginx is now running as a Linux process inside the container.
Step 9: Port mapping kicks in
Docker sets up forwarding of incoming connections from port 8080 on the host machine to port 80 in the container.
Step 10: The application is reachable from the browser
Now open a browser and go to:
http://localhost:8080
and you will see the Nginx welcome page.
Complete runtime flow
User
│
│ docker run -d -p 8080:80 nginx
▼
Docker CLI
│
▼
Docker API
│
▼
Docker Engine
│
├── Check Image
│
├── Pull Image if Required
│
├── Prepare Networking
│
├── Configure Storage
│
├── Apply Security Settings
│
▼
containerd
│
▼
containerd-shim
│
▼
runc
│
▼
Linux Kernel
│
▼
Nginx Process
│
▼
Host Port 8080 → Container Port 80
│
▼
http://localhost:8080
In other words, many technologies work together behind one simple Docker command.
11. Docker architecture — A simple real-life analogy
A simple example helps you remember Docker's components.
Imagine Docker is a big company.
Different departments in this company do different jobs.
| Docker Component | Real-Life Analogy |
|---|---|
| Docker CLI | Reception Desk |
| Docker API | Internal Communication System |
| Docker Engine | Operations Director |
| BuildKit / Buildx | Factory |
| Images | Packaged Products |
| Registry | Warehouse / Distribution Center |
| Networking | Communication Infrastructure |
| Volumes | Permanent Storage Room |
| Authentication | Identity Verification |
| Authorization | Access Permission System |
| Security | Rules, Locks and Restricted Areas |
| containerd | Operations Manager |
| containerd-shim | Container Supervisor |
| runc | The worker who starts the container |
| Linux Kernel | Core Infrastructure |
Example
Say you walk up to the company's reception and say:
Please start a new Nginx server for me.
The reception desk, that is the Docker CLI, takes your request.
The internal communication system, that is the Docker API, passes the request to the operations director, Docker Engine.
Docker Engine coordinates preparing the image, networking and storage that are needed.
Then containerd manages the runtime operations.
runc starts the actual container process with the help of the Linux kernel.
Finally, the Nginx server starts running.
That is how each Docker component carries out its own responsibility.
12. Docker's most important components — Quick reference
| Component | Main job |
|---|---|
| Docker CLI | Takes user commands and sends API requests |
| Docker API | Communication between external tools and Docker Engine |
| Docker Engine | Coordinates Docker resources and container operations |
| BuildKit | Builds Docker images efficiently |
| Buildx | Provides an advanced image building interface |
| Docker Images | The packaged application a container runs from |
| Registry | Stores and distributes Docker images |
| Networking | Container communication and port publishing |
| Volumes | Stores persistent application data |
| Authentication | Verifies user or service identity |
| Authorization | Controls which operations are permitted |
| Security | Container isolation and privilege restrictions |
| containerd | Manages the container runtime lifecycle |
| containerd-shim | Helps supervise running containers |
| runc | Creates the container process from the OCI configuration |
| Linux Kernel | Provides process execution, isolation and resource control |
13. Docker Engine vs containerd vs runc — The real difference
To understand Docker's architecture, you need to be clear on the difference between these three components.
Docker Engine
Docker Engine is a higher-level container management platform.
It manages images, containers, networks, volumes and the Docker API.
Its big advantage is a developer-friendly interface and workflow.
containerd
containerd is the container runtime management layer.
It handles image content, snapshots and container lifecycle operations.
It can be used in both the Docker and Kubernetes ecosystems.
runc
runc is a low-level OCI runtime.
It uses the Linux kernel's isolation mechanisms to create and start the actual container process.
Comparison
| Feature | Docker Engine | containerd | runc |
|---|---|---|---|
| Abstraction Level | High | Medium / Low | Low |
| Primary Responsibility | Container Platform Management | Container Runtime Management | Container Process Creation |
| Typical Users | Developers, DevOps | Runtime / Platform Engineers | Runtime Implementations |
| CLI | docker | ctr, nerdctl | runc |
| Kubernetes Runtime | Not directly via built-in dockershim | Yes, through CRI integration | Used underneath compatible runtimes |
| Image Building | BuildKit integration | Not a full Docker build platform | No |
14. Final takeaway — Docker is really a complete technology stack
The easiest way to understand Docker's architecture is to remember each component's responsibility.
Docker CLI
→ The commands we use
Docker API
→ How tools talk to Docker Engine
Docker Engine
→ Manages Docker resources and operations
BuildKit
→ Builds Docker images
Volumes
→ Stores persistent data
Authentication
→ Verifies identity
Authorization
→ Controls access permissions
Security
→ Applies container isolation and restrictions
containerd
→ Manages the container runtime lifecycle
containerd-shim
→ Supervises running containers
runc
→ Creates and starts the actual container process
Linux Kernel
→ The foundation of container execution and isolation
The most important point: Docker does not create a new operating system. Linux containers run as isolated processes using the host's kernel features.
To run Linux containers on macOS and Windows, Docker Desktop usually uses a Linux virtual machine or a WSL 2 environment.
So a Docker container and a virtual machine are not the same thing.
A container usually runs in a shared kernel environment, while a virtual machine runs its own guest operating system and kernel.
This architectural difference is what makes containers lightweight and quick to deploy for many workloads.
Official References
To learn more about Docker's internal architecture, read the documentation below.
- Docker Engine Overview
- Docker Architecture Overview
- Docker Engine API
- Docker BuildKit
- Docker Volumes
- Docker Security
- containerd Documentation
- runc — OCI Runtime
- Open Container Initiative
Final words
When we type docker run nginx, the whole thing looks very simple from the outside.
But behind it, the Docker CLI, the API, Docker Engine, containerd, containerd-shim, runc and the Linux kernel all work together.
For a developer, knowing Docker commands is important. But if you want to work in DevOps, platform engineering, cloud infrastructure or Kubernetes, understanding Docker's internal architecture matters even more.
Because then you will not only be able to run containers, you will also be able to troubleshoot systematically: why a container is not starting, where the runtime is failing, and where a networking or storage problem lies.
Docker is not just a tool for running containers — it is a complete platform for application build, distribution and container lifecycle management.
