Docker packages an application together with its runtime, libraries and configuration into an image, and runs that image as an isolated process called a container. Containers share the host's Linux kernel, so they start in well under a second and use far less memory than virtual machines, while still giving each application its own filesystem, network stack and process tree. This page summarises the key ideas, then explains the architecture behind docker run, how images are built from layers, a complete worked example, and the trade-offs and mistakes that matter in practice.
Containerization: Docker enables the creation and management of containers, which are lightweight, portable, and isolated environments that bundle an application and its dependencies together. This ensures consistency across different environments, such as development, testing, and production.
Images: Docker containers are created from images, which are read-only templates with instructions on how to create a container. Docker images are typically built using a Dockerfile, which contains a series of instructions to assemble the image.
Dockerfile: A text file that contains all the commands to build a Docker image. It specifies the base image, environment variables, files to be included, and the commands to run within the container.
Portability: Docker containers can run consistently across any system that supports Docker, whether it's a local machine, a data center, or in the cloud. This eliminates the "it works on my machine" problem.
Isolation: Each Docker container runs in its own isolated environment, with its own filesystem, networking, and processes, which ensures that the containerized application doesn't interfere with other applications on the host system.
Efficiency: Containers are more lightweight compared to virtual machines (VMs) because they share the host system's OS kernel, which results in lower overhead and faster startup times.
Version Control: Docker images are versioned, allowing developers to roll back to previous versions if needed. This is particularly useful for maintaining consistency and reproducibility in development and production environments.
Scalability: Docker simplifies the process of scaling applications. By using tools like Docker Compose and Docker Swarm, multiple containers can be managed, orchestrated, and scaled to handle increased load.
Security: Containers run in isolated environments, which enhances security by limiting the scope of access to the host system. Additionally, Docker provides security features like image scanning to detect vulnerabilities in Docker images.
Ecosystem: Docker has a vast ecosystem, including Docker Hub (a repository for Docker images), Docker Compose (for defining and running multi-container Docker applications), and Docker Swarm/Kubernetes (for container orchestration).
The docker command is only a client. It sends API calls to the Docker daemon, dockerd, usually over /var/run/docker.sock. The daemon pulls the image from a registry if it is not cached, then hands the work to containerd, which manages images and container lifecycles, and containerd calls an OCI runtime, normally runc, to create the process. Isolation comes from Linux kernel features rather than from a hypervisor: namespaces give the process its own view of process IDs, mounts, network interfaces, hostname and users, and cgroups limit CPU, memory and I/O. On macOS and Windows, Docker Desktop runs a small Linux VM to supply that kernel.
Each Dockerfile instruction that changes the filesystem produces a layer, identified by a content hash. Layers are shared between images and cached between builds; when you rebuild, Docker reuses every layer up to the first instruction whose inputs changed. A running container adds a thin writable layer on top using a union filesystem (overlay2 on most Linux hosts). Anything written there disappears when the container is removed, which is why persistent data belongs in volumes.
Order instructions from least to most frequently changing. Copying requirements.txt and installing dependencies before copying the source code means an ordinary code edit rebuilds only the last layer.
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
docker build -t myapp:1.0 .
docker run -d --name myapp -p 8000:8000 \
-e DATABASE_URL=postgresql://db/app \
-v myapp-data:/app/data \
--memory 512m --cpus 1 myapp:1.0
docker ps # running containers
docker logs -f myapp # stream stdout/stderr
docker exec -it myapp sh # shell inside the container
docker stop myapp && docker rm myapp
docker image prune # remove dangling images
For several cooperating containers, describe them in a compose.yaml file and start them with docker compose up -d; Compose creates a private network where services reach each other by name.
| Option | Isolation | Start time | Use when |
|---|---|---|---|
| Docker container | Kernel namespaces and cgroups | Sub-second | Packaging and running services consistently across machines |
| Virtual machine | Hypervisor, separate kernel | Seconds to minutes | Untrusted workloads, different OS kernels |
| Podman | Same as Docker, daemonless, rootless by default | Sub-second | No root daemon allowed; Docker-compatible CLI |
| Kubernetes | Containers plus scheduling | Seconds | Running many containers across a cluster with self-healing and scaling |
USER and avoid --privileged./var/run/docker.sock into a container, or adding a user to the docker group, grants control of the host.-p 8000:8000 inserts iptables rules ahead of ufw's; bind to 127.0.0.1:8000:8000 if the port should not be public.latest tags. They are mutable; pin versions or digests for reproducible deployments.