Docker: Containers, Images, and the Basics
#docker#containerization#virtualization#containers#devops
Docker is a platform for building, shipping, and running applications in containers. Containers package your app and its dependencies into a single runnable unit that behaves the same everywhere: on your laptop, in CI/CD, or in the cloud.
Why containers?
- Consistency: Same image runs the same way in dev, test, and production.
- Isolation: Each container has its own filesystem and network namespace.
- Portability: Build once, run on any host that supports Docker (or another container runtime).
- Efficiency: Containers share the host kernel and start quickly compared to virtual machines.
Docker architecture
Docker uses a client–server model. When you run docker run or docker build, the Docker client (CLI) talks to the Docker daemon (dockerd), which does the real work.
+------------------+ REST API (e.g. over socket) +------------------------+
| Docker Client | -----------------------------> | Docker Daemon |
| (docker CLI) | | (dockerd) |
+------------------+ +------------+------------+
|
+------------------------------------------------------------+-----------------------------+
| v |
| +-------------+ +-------------+ +-----------------+ +---------------------+ |
| | Images & | | containerd | | runc | | Network, volumes, | |
| | layer | | (runtime |->| (OCI runtime) | | build, etc. | |
| | storage | | lifecycle) | | creates actual | | | |
| | | | | | container | | | |
| +-------------+ +-------------+ +-----------------+ +---------------------+ |
+---------------------------------------------------------------------------------------------+
Host
- Docker daemon (dockerd): Builds images, manages containers, images, networks, and volumes. Listens for requests from the client.
- containerd: Container runtime that manages the lifecycle of containers (start, stop, delete). Docker delegates container execution to containerd.
- runc: Low-level OCI-compliant runtime that creates and runs the actual process in a constrained environment (namespaces, cgroups). containerd calls runc to create the container process.
- Image and layer storage: Images and their layers are stored on disk (e.g. under
/var/lib/dockeron Linux). The daemon uses a storage driver (e.g.overlay2) to assemble layers into a container filesystem.
So the flow for docker run is: Client → dockerd → containerd → runc → your process runs inside the container.
Docker image layers
An image is not a single file; it is made of read-only layers stacked on top of each other. Each Dockerfile instruction that changes the filesystem (e.g. RUN, COPY, ADD) typically creates a new layer.
How layers work
- Read-only: Every layer is immutable. Once built, it is reused by any image or container that needs it.
- Union mount (e.g. OverlayFS): When you run a container, the storage driver merges all image layers into a single view. The container also gets a thin writable layer on top for any changes at runtime (e.g. creating files). That writable layer is discarded when the container is removed unless you use a volume.
- Layering and caching: If you rebuild an image, Docker reuses cached layers until an instruction changes. Then that instruction and everything after it are rebuilt.
Example: this Dockerfile produces several layers:
FROM node:20-alpine # Layer 1: base image (many layers from node:20-alpine)
WORKDIR /app # Layer 2: create /app (metadata + empty dir in layer)
COPY package*.json ./ # Layer 3: add files
RUN npm ci # Layer 4: install deps (new files in this layer)
COPY . . # Layer 5: add app code
CMD ["node", "app.js"] # Layer 6: metadata only (no new filesystem layer)
Only instructions that change the filesystem add a new filesystem layer; WORKDIR, ENV, CMD, EXPOSE etc. often add only metadata.
Why layer order matters
- Cache reuse: Put instructions that change less often (e.g.
COPY package*.json+RUN npm ci) before instructions that change often (e.g.COPY . .). That way code changes do not invalidate the dependency-install layer. - Smaller images: Fewer or smaller layers mean less to pull and store. Combine related
RUNcommands to avoid extra layers (e.g.RUN apt update && apt install -y ...in one line).
Inspecting layers
# Show the layers (history) of an image
docker history my-app:latest
# Inspect image details, including layer IDs and config
docker image inspect my-app:latest
Summary: image layers
| Idea | Detail |
|---|---|
| Layer | One read-only filesystem delta (or metadata). Many layers form one image. |
| Union filesystem | Layers are stacked; the container sees one merged filesystem plus a writable top layer. |
| Cache | Rebuild reuses layers until an instruction changes; then that layer and all below are rebuilt. |
| Best practice | Order Dockerfile so rarely changing steps come first; combine RUN steps to reduce layers. |
Core concepts
| Term | Meaning |
|---|---|
| Image | Read-only template: OS layer + app + dependencies. Built from a Dockerfile or pulled from a registry. |
| Container | A running instance of an image. You can run many containers from the same image. |
| Dockerfile | Text file with instructions to build an image (e.g. FROM, RUN, COPY, CMD). |
| Registry | Storage for images (Docker Hub, Azure ACR, AWS ECR, etc.). |
Dockerfile example
Each instruction that modifies the filesystem adds a layer (see Docker image layers above). This example keeps dependency install in its own layer so that layer can be cached when only app code changes:
# Use an official runtime as base
FROM node:20-alpine
# Set working directory inside the container
WORKDIR /app
# Copy dependency list and install
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Expose port and define default command
EXPOSE 3000
CMD ["node", "server.js"]
Build the image:
docker build -t my-app:latest .
Running containers
# Run in foreground (logs in terminal)
docker run -p 8080:3000 my-app:latest
# Run in background (detached)
docker run -d --name web -p 8080:3000 my-app:latest
# Run with environment variable
docker run -e NODE_ENV=production -p 8080:3000 my-app:latest
# Remove container when it stops
docker run --rm -p 8080:3000 my-app:latest
Essential commands
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# List images
docker images
# Stop a container
docker stop web
# Remove a container
docker rm web
# Remove an image
docker rmi my-app:latest
# View logs
docker logs web
# Execute a command inside a running container
docker exec -it web sh
Docker Compose (multi-container)
For apps that need several services (app + database + cache), use a docker-compose.yml:
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Run everything:
docker compose up -d
Summary
- Images are built from Dockerfiles (or pulled); containers are running instances of images.
- Use
docker buildanddocker runfor single services; use Docker Compose for multi-service setups. - Combine with CI/CD and orchestration (e.g. Kubernetes) to run containers at scale.
Comments