Docker: Fundamentals
Master the core concepts of containerization including images, containers, networking, and the Docker architecture.
Why Docker? The Problems It Solves
Consider the following scenario: You have built an application that works perfectly on your machine. You hand it to a colleague, and it fails immediately. The culprit? Different Python versions, missing libraries, or conflicting configurations. Docker eliminates this entire class of problems.
Common Development Challenges
Dependency Hell
Different projects require different versions of libraries, languages, and tools, leading to conflicts and complex virtual environment management.
Environment Parity
Code that works perfectly on a developer's laptop fails in production due to OS differences, missing dependencies, or configuration mismatches.
Onboarding Time
New team members spend days setting up development environments, installing tools, and troubleshooting configuration issues.
Resource Efficiency
Traditional VMs consume significant resources, limiting the number of applications that can run on a single server.
How Docker Solves These Problems
Isolated Environments
Each container has its own filesystem, network, and process space, eliminating conflicts between applications.
Reproducible Builds
Dockerfiles define exact steps to build an environment, ensuring consistency across all stages of development.
Instant Setup
New developers can start with a simple `docker run` command, eliminating complex installation procedures.
Efficient Layering
Docker's layer system shares common components between containers, dramatically reducing disk usage and memory overhead.
Essential Docker Commands
Before running any Docker commands, it helps to understand the mental model: Docker images are like recipes (blueprints), while containers are the actual dishes you create from those recipes. You can make many containers from the same image, and each one runs independently.
Core Operations
The following examples demonstrate the core concepts. Start with simple commands and build up to more complex workflows.
Running Containers
When to use: Start here when learning Docker or when you need to quickly test something in a clean environment.
# Run an interactive Ubuntu container
docker run -it ubuntu:22.04 bash
# You are now inside a minimal Linux system
cat /etc/os-release && exit
The -it flags create an interactive terminal session. When you type exit, the container stops.
Web Server Deployment
When to use: When you need to run a service in the background, such as a web server, database, or API.
# Run Nginx web server in the background
docker run -d -p 8080:80 --name my-web nginx
# Visit http://localhost:8080, then clean up
docker stop my-web && docker rm my-web
The -d flag runs the container in the background. The -p 8080:80 maps your machine's port 8080 to the container's port 80.
Building Custom Images
When to use: When you need to package your own application with its specific dependencies and configuration.
# Dockerfile - save this file, then build with: docker build -t my-app .
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
# Build and run your custom image
docker build -t my-app .
docker run -d -p 5000:5000 my-app
A Dockerfile defines your environment step by step. Docker caches each step, so rebuilds are fast when only your code changes.
Using Docker Compose
When to use: When your application needs multiple services (web server + database, for example) that work together.
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis
redis:
image: redis:alpine
# Start all services with one command
docker-compose up -d
# Stop everything
docker-compose down
Compose automatically creates a network where services can find each other by name. Your web service can connect to redis without knowing its IP address.
Key Takeaways
- Images are blueprints; containers are running instances
- Dockerfiles define how to build images reproducibly
- Port mapping connects container services to your host
- Docker Compose manages multi-container applications
- Volumes persist data beyond container lifecycle
Understanding Container Technology
Before diving into Docker’s specific implementation, it helps to understand how containers differ from traditional virtualization. This context will help you make informed decisions about when to use each approach.
Containers vs Virtual Machines
Consider the following when choosing between containers and VMs:
| Factor | Choose Containers | Choose VMs |
|---|---|---|
| Startup time matters | Yes - seconds vs minutes | Boot time acceptable |
| Running many instances | Yes - minimal overhead | Fewer, larger workloads |
| Need different OS | No - share host kernel | Yes - run Windows on Linux |
| Security isolation | Process-level sufficient | Need hardware-level isolation |
| Legacy applications | May need refactoring | Run as-is |
Containers are lightweight, resource-efficient, and portable, making them suitable for modern, scalable applications. Virtual machines provide strong isolation, full OS support, and hardware emulation but can be resource-intensive and slower to start up.
Container Pros/Cons
Pros
Share the host OS kernel, minimal overhead
Start in seconds for rapid deployment
Higher density on single host
Consistent deployment across environments
Applications run without interference
Cons
Limited cross-platform compatibility
Weaker isolation than VMs
Not suitable for kernel modifications
Virtual Machine Pros/Cons
Pros
Complete OS separation for security
Run any OS version or distribution
Support legacy and platform-specific apps
Extensive tooling and management
Cons
Full OS stack overhead
Minutes to boot and initialize
Duplicated OS and libraries
Manual dependency management
What Containers Guarantee
Containers provide strong guarantees in several areas:
- Application dependencies: All libraries and tools are bundled together, eliminating “missing dependency” errors
- Configuration: Environment settings travel with the container, making deployments reproducible
- Isolation: Each container has its own filesystem and process space, preventing conflicts between applications
- Portability: The same container runs on any machine with Docker installed
What Containers Cannot Guarantee
However, some factors remain outside container control:
- Kernel features: Containers share the host kernel, so a container expecting Linux 5.x features will not work on a host running Linux 4.x
- Hardware access: GPU acceleration, specialized devices, and host-specific resources may behave differently across machines
- Resource limits: CPU and memory constraints vary by host, affecting performance consistency
- Platform differences: A Linux container cannot run natively on Windows without a Linux VM layer
Practical guidance: For maximum portability, avoid dependencies on specific kernel versions or hardware features. When these are unavoidable, document the requirements clearly.
Now that we understand what containers do, let’s briefly look at how Docker implements them. You do not need to memorize these details to use Docker effectively, but understanding the architecture helps when troubleshooting or optimizing performance.
Docker Architecture Overview
Docker uses a layered architecture where each component has a specific responsibility:
| Component | Role | When You Interact With It |
|---|---|---|
| Docker CLI | User interface | Every docker command you run |
| Docker Daemon | Manages containers, images, networks | Runs in background |
| containerd | Container lifecycle management | Rarely directly |
| runc | Actually runs containers | Never directly |
Container Runtime Basics
At its core, a container is defined by:
- Filesystem: What files the container can see (its “root filesystem”)
- Namespaces: Isolation boundaries for processes, network, users, and more
- Cgroups: Resource limits for CPU, memory, and I/O
- Security: Capabilities, seccomp profiles, and mandatory access controls
The Open Container Initiative (OCI) standardizes these specifications, allowing containers to run on any compliant runtime.
Note: Most users never interact with these low-level components directly. Docker’s CLI abstracts away this complexity while giving you control when needed.
Docker Network Architecture
Networking is often the trickiest part of containerization. Containers need to communicate with each other, with the host, and with external services, all while maintaining isolation. Docker provides several network drivers for different scenarios.
Network Driver Quick Reference
| Driver | Use Case | Isolation | Performance |
|---|---|---|---|
| bridge | Default for standalone containers | Good | Good |
| host | Maximum performance needed | None | Best |
| overlay | Multi-host communication (Swarm) | Good | Good |
| macvlan | Container needs real IP on network | Varies | Good |
| none | Complete network isolation | Maximum | N/A |
How Bridge Networking Works
When you run a container without specifying a network, Docker uses the default bridge network. Here is what happens:
- Docker creates a virtual network interface pair (veth)
- One end attaches to the container, the other to a bridge on the host
- The container gets an IP address from Docker’s internal DHCP
- Containers on the same bridge can communicate by IP
- For external access, Docker uses NAT and port mapping
Key insight: Containers on the same user-defined bridge network can find each other by container name (automatic DNS). The default bridge does not have this feature, which is why creating custom networks is recommended.
When to Use Each Network Type
- bridge: Development, single-host deployments, isolated applications
- host: Performance-critical applications, when container port must match host port
- overlay: Docker Swarm services spanning multiple hosts
- macvlan: When container must appear as physical device on network (legacy integration)
- none: Security-sensitive workloads that should have no network access