A container packages an application with its execution environment to run identically anywhere.
Container
Process Isolation
A container is an isolated process running on the host OS. There is no guest OS. It shares the host kernel while isolating the process tree, network, and filesystem.
Namespaces partition process lists, network interfaces, and filesystems into independent spaces. A process inside a container cannot see other containers.
cgroups (Control Groups) cap CPU, memory, and disk I/O usage. Without them, a single container could consume all host resources.
Comparison with VMs
Process isolation through namespaces and cgroups differs fundamentally from VMs.
A VM installs a full guest OS on top of a hypervisor. This provides hardware-level isolation, but including an entire OS makes images large and startup slow.
A container shares the host kernel and isolates at the process level only. No guest OS means lighter images and faster startup. The isolation level is lower than a VM, but sufficient for most deployment scenarios.
flowchart TB
subgraph vm["VM"]
direction TB
HW1["Hardware"] --> HV["Hypervisor"]
HV --> G1["Guest OS + App"]
HV --> G2["Guest OS + App"]
end
subgraph ct["Container"]
direction TB
HW2["Hardware"] --> OS["Host OS\nShared Kernel"]
OS --> CR["Container Runtime"]
CR --> C1["App"]
CR --> C2["App"]
end
Docker Architecture
Docker operates as a client-server architecture.
flowchart LR
CLI["Docker CLI"] -->|REST API| D["Docker Daemon\ndockerd"]
D --> CTD["containerd"]
CTD --> RUNC["runc"]
RUNC --> C1["Container"]
RUNC --> C2["Container"]
Running docker run or docker build sends the command from the Docker CLI to the Docker Daemon (dockerd) via REST API. The Daemon is the core process managing container lifecycles.
Two more layers exist below that. containerd handles container execution, image management, and storage. runc configures namespaces and cgroups to start the container process. runc implements the OCI (Open Container Initiative) standard.
Because the layers are separate, containers can run with containerd alone, without the Docker Daemon. Kubernetes dropped its Docker dependency for this reason.
Core Concepts
Image
Running a container requires an image. An image is a read-only template containing application code, runtime, libraries, and configuration files.
Images use a layered structure. Each layer adds only the changes on top of the previous one. Multiple images sharing common layers reduces disk usage and build time.
Container
A container is a running instance created from an image. It adds a writable layer on top of the image’s read-only layers. Multiple containers run independently from the same image.
Deleting a container removes its writable layer. Persistent data requires volumes to store separately on the host.
Registry
A registry stores and distributes images. Docker Hub is the most widely used public registry, and organizations often run private ones.
Storing an image to a registry is a push; retrieving one is a pull.
flowchart LR
DF["Dockerfile"] -->|docker build| IMG["Image"]
IMG -->|docker run| CT["Container"]
IMG -->|docker push| REG["Registry"]
REG -->|docker pull| IMG2["Image"]
Dockerfile
Building an image requires a Dockerfile. It describes base image selection, application copying, dependency installation, and run commands in order.
Key Instructions
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
FROM specifies the base image. It is the starting point of every Dockerfile. WORKDIR sets the working directory, and COPY brings files from the host into the image.
RUN executes commands during the build. It handles dependency installation and compilation. Each RUN instruction creates a new layer.
EXPOSE documents the port the container listens on. Actual port binding happens with docker run -p. CMD sets the default command to run when the container starts.
CMD vs ENTRYPOINT
They look similar but serve different purposes.
CMD is the default startup command. Passing arguments to docker run replaces CMD entirely. ENTRYPOINT fixes the command that always executes. Arguments from docker run append after ENTRYPOINT.
# CMD — can be replaced by docker run arguments
CMD ["node", "server.js"]
# ENTRYPOINT — always runs node; accepts additional arguments
ENTRYPOINT ["node"]
CMD ["server.js"]
Using both together, ENTRYPOINT defines the executable and CMD provides default arguments. Running docker run <image> worker.js replaces only the CMD portion. A common pattern for distributing CLI tools as containers.
Multi-stage Build
Multiple FROM instructions in a single Dockerfile separate build stages. Build tools and source stay in the build stage; the final image contains only the artifacts needed for execution.
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o server .
# Run stage
FROM alpine:3.19
COPY --from=builder /app/server /server
CMD ["/server"]
The compiler and source are excluded from the final image, reducing its size. The effect is significant for compiled languages like Go.
Docker Compose
Real-world services run multiple containers together: a web server, a database, a cache. Managing each with individual docker run commands gets cumbersome.
Docker Compose defines and manages multi-container applications in a single file.
Basic Structure
services:
api:
build: .
ports:
- "8080:3000"
depends_on:
- db
- redis
environment:
DATABASE_URL: postgres://user:pass@db:5432/mydb
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
db-data:
services defines each container. build specifies a Dockerfile path; image uses an existing one.
volumes persists data outside the container. Data survives container deletion.
depends_on sets the startup order between services. It does not wait for a service to become “ready,” so the application needs its own retry logic.
Common Commands
docker compose up starts all services. Add -d to run in the background. docker compose down removes containers and networks but preserves volumes. docker compose logs -f follows logs in real time.
Wrap-up
A container is a process isolation technology. Docker manages it through images, containers, and registries. Building with Dockerfile and composing with Docker Compose maintains identical environments from development to deployment.