Lecture 03. Containerization with Docker
Overview
This lecture introduces Docker, a platform that revolutionizes application development, deployment, and management through containerization technology. Docker enables developers to package applications with all necessary dependencies into standardized units called containers, ensuring consistent behavior across different environments.
Key Topics
- Fundamentals of Docker: Learn what Docker is and how it differs from traditional virtualization technologies
- Container Architecture: Understand how Docker leverages Linux kernel features (namespaces and cgroups) to create lightweight, isolated environments
- Docker Ecosystem: Explore Docker images, containers, registries, and the Docker Engine
- Development Workflow: Learn how to write Dockerfiles, build images, and run containers
- Practical Applications: Examine real-world use cases for Docker in development, testing, and production environments
Learning Objectives
By the end of this lecture, you will be able to:
- Understand the technical foundations of containerization
- Create and manage Docker containers using essential commands
- Write effective Dockerfiles for application packaging
- Implement Docker in your development workflow
- Compare Docker containers with traditional virtual machines
Why Docker Matters
Docker addresses the classic "it works on my machine" problem by providing a consistent, portable runtime environment. This technology has become fundamental in modern development practices, cloud computing, and DevOps, making it an essential skill for today's software engineers.
Further Reading
For deeper insights into Docker technologies, check out these articles:
- Exploring Lightweight Docker Base Images: Alpine, Slim, and Debian Releases
- Optimizing Docker Images for Faster Deployments and Efficient Resource Usage
- Distroless Docker Images Explained: Secure, Lightweight, and Ready for Production
Docker
📚 Table of Contents
- What is Docker?
- Evolution of Docker: From Linux Kernel to Container Revolution
- Containers, Images, and Registries
- Why Docker Matters in the Software Development Life Cycle (SDLC)
- Docker vs Virtual Machines: A Technical Comparison
- How Docker Uses the OS Kernel: Namespaces & cgroups
- User Space vs Kernel Space in Docker
- Writing a Simple Dockerfile
- Conclusion
🐳 What is Docker?
Docker is a platform for developing, shipping, and running applications inside lightweight containers. It ensures your software runs reliably when moved from one environment to another—be it from a developer’s laptop to testing, staging, or production environments.
In Simple Terms:
Docker = Standardized Software Environment + Speed + Portability
🧬 Evolution of Docker: From Linux Kernel to Container Revolution
Docker wasn’t built from scratch. It evolved by wrapping powerful but complex Linux kernel features—namespaces and cgroups—into an easy-to-use tool.
🔹 Linux Namespaces:
Introduced in the Linux kernel to isolate processes, users, network, and filesystems. Each process thinks it's running on a dedicated OS.
🔹 Linux Control Groups (cgroups):
These control how much CPU, memory, and I/O resources each group of processes can use.
🔹 UnionFS:
A layered filesystem Docker uses to compose images efficiently by stacking file changes.
ASCII Diagram: Traditional vs Dockerized Process
Before Docker:
+----------------------+
| Linux Host |
|----------------------|
| App A, B, C | ← Global processes
+----------------------+
With Docker:
+----------------------+
| Container A | Isolated PID, FS |
| Container B | Own User, Net |
| Container C | Limited Resources|
+----------------------+Docker made it all accessible with a simple CLI/API and Docker Engine.
📦 Containers, Images, and Registries
🔸 What is a Container?
A container is an isolated execution environment for running applications. It includes the app, libraries, dependencies, and runtime—but shares the host kernel.
Container = App + Dependencies + Libraries + ConfigsEach container is ephemeral, meaning it can be started, stopped, moved, or deleted quickly.
🔸 What is a Docker Image?
A Docker image is a read-only blueprint for a container. It defines:
- What the container contains (code, binaries, configs)
- How the container behaves (start commands)
Images are built using a Dockerfile.
Layers in an Image (UnionFS):
Base Image (e.g., ubuntu:20.04)
+----------------------+
| App Dependencies |
+----------------------+
| App Source Code |
+----------------------+
| Run Instructions |
+----------------------+🔸 What is a Docker Registry?
A Docker registry is a storage and distribution system for images.
- Docker Hub: Default public registry
- Private registries: For enterprise use (e.g., AWS ECR, GitHub Container Registry)
You pull images from registries and push them when publishing your own.
# Pull official nginx image
docker pull nginx
# Push your image to Docker Hub
docker push yourname/myapp:1.0🔁 Why Docker Matters in the Software Development Life Cycle (SDLC)
Docker brings consistency, scalability, and speed to every phase of the SDLC.
🔨 1. Development
- Uniform environments across teams
- Quick setup and teardown of dev environments
🧪 2. Testing
- Test on production-like containers
- Use parallel, isolated test instances
🚀 3. Deployment
- Consistent container runs on any server or cloud
- Seamless with CI/CD pipelines (GitHub Actions, GitLab CI)
📈 4. Operations
- Scales easily with Kubernetes, Docker Swarm
- Simplifies monitoring and rolling updates
🆚 Docker vs Virtual Machines: A Technical Comparison
📌 Key Differences
| Feature | Virtual Machine | Docker Container |
|---|---|---|
| Boot Time | Minutes | Seconds |
| OS Requirements | Full Guest OS per VM | Shares Host OS Kernel |
| Size | GBs | MBs |
| Performance | Slower (Hypervisor overhead) | Near-native |
| Portability | Limited | High (Run Anywhere) |
ASCII Diagram: VM vs Docker
Traditional VM:
+-------------+
| App |
| Guest OS |
| Hypervisor |
| Host OS |
| Hardware |
+-------------+
Docker:
+-------------+
| App |
| Docker Engine
| Host OS |
| Hardware |
+-------------+🧠 How Docker Uses the OS Kernel: Namespaces & cgroups
Namespaces (Isolation)
Each container gets its own view of the system:
- PID namespace: Unique process tree
- Net namespace: Own network interfaces
- Mount namespace: Own filesystem mounts
Control Groups (Resource Limits)
Docker sets limits using cgroups:
- CPU shares
- Memory limits
- Block I/O constraints
⚙️ User Space vs Kernel Space in Docker
🔹 Kernel Space:
- Manages core OS operations
- Shared among containers and host
🔹 User Space:
- Where applications run
- Isolated in each container
Diagram:
+----------------------------+
| Kernel Space | ← Shared
+----------------------------+
| Container A: User Space |
| Container B: User Space |
| Container C: User Space |
+----------------------------+Docker containers are isolated in user space, but share the host kernel for efficient resource usage.
🧾 Writing a Simple Dockerfile
Let’s package a basic Python app using Docker.
📁 File Structure
myapp/
├── app.py
└── Dockerfileapp.py
print("Hello from inside Docker!")Dockerfile
# Start from a Python base image
FROM python:3.10-slim
# Set working directory
WORKDIR /app
# Copy source code
COPY app.py .
# Define container start command
CMD ["python", "app.py"]Build & Run
docker build -t hello-docker .
docker run hello-docker🖨️ Output:
Hello from inside Docker!✅ Conclusion
Docker is not just another tool—it’s a paradigm shift in how we build, ship, and run software. By combining decades of operating system research (namespaces, cgroups) with a friendly interface, Docker democratized containerization for developers and enterprises alike.
Whether you're creating monoliths, microservices, or distributed systems, Docker empowers you with:
- Speed
- Consistency
- Isolation
- Portability
📚 Further Reading
Docker Hands-On Lab
This lab provides practical exercises to reinforce the Docker concepts covered in the lecture. These hands-on activities will help you build confidence in using Docker for development and deployment scenarios.
Prerequisites
- Docker Engine installed on your system
- Basic command line familiarity
- Text editor of your choice
Exercise 1: Getting Started with Docker
1.1 Verify Docker Installation
Ensure Docker is properly installed on your system:
docker --version
docker info1.2 Running Your First Container
Let's run an interactive container using the official Ubuntu image:
docker run -it --rm ubuntu bashInside the container, try some commands:
ls
cat /etc/os-release
apt updateType exit to leave the container.
Exercise 2: Building Custom Images
2.1 Create a Simple Python Web Application
Create a directory for your project:
mkdir docker-lab
cd docker-labCreate a file named app.py:
from http.server import BaseHTTPRequestHandler, HTTPServer
import socket
import json
class WebServer(BaseHTTPRequestHandler):
def _set_response(self, content_type="text/plain"):
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
def do_GET(self):
if self.path == '/':
# Serve the main page as plain text
self._set_response("text/plain")
hostname = socket.gethostname()
response = f"""
Hello from Docker!
This page is being served from a Docker container.
Container hostname: {hostname}
"""
self.wfile.write(response.encode('utf-8'))
elif self.path == '/api/info':
# Serve JSON API response
self._set_response("application/json")
data = {
"hostname": socket.gethostname(),
"python_version": socket.python_version(),
"container": True
}
self.wfile.write(json.dumps(data).encode('utf-8'))
else:
self._set_response()
self.wfile.write(b"404 - Not Found")
def run(server_class=HTTPServer, handler_class=WebServer, port=3000):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f"Starting server on port {port}...")
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
print("Server stopped.")
if __name__ == '__main__':
run()2.2 Create a Dockerfile
Create a file named Dockerfile:
# Use Python as the base image
FROM python:3.9-slim
# Set working directory
WORKDIR /app
# Copy application file
COPY app.py .
# Expose the port the app runs on
EXPOSE 3000
# Command to run the application
CMD ["python", "app.py"]2.3 Build and Run Your Image
Build your Docker image:
docker build -t my-python-app .Run the container:
docker run -d -p 3000:3000 --name web-app my-python-appTest your application:
# Using curl for plain text response
curl http://localhost:3000
# Using curl for JSON API
curl http://localhost:3000/api/info2.4 Explore Your Container
Check the running containers:
docker psView container logs:
docker logs web-appExecute commands in the running container:
docker exec -it web-app bashStop and remove your container:
docker stop web-app
docker rm web-appExercise 3: Advanced Docker Concepts
3.1 Docker Networks
Create a custom bridge network:
docker network create mynetworkRun containers on the network:
docker run -d --name container1 --network mynetwork alpine sleep infinity
docker run -d --name container2 --network mynetwork alpine sleep infinityTest container communication:
docker exec container1 ping -c 3 container2List networks and inspect:
docker network ls
docker network inspect mynetworkClean up:
docker stop container1 container2
docker rm container1 container2
docker network rm mynetwork3.2 Docker Volumes
Create a named volume:
docker volume create mydataRun a container with the volume:
docker run -d --name vol-test -v mydata:/data alpine sh -c "while true; do date >> /data/dates.txt; sleep 10; done"Inspect the volume data:
docker exec vol-test cat /data/dates.txtUse a different container to access the same volume:
docker run --rm -v mydata:/data alpine cat /data/dates.txtClean up:
docker stop vol-test
docker rm vol-test
docker volume rm mydataConclusion
This lab has covered:
- Running containers with Docker
- Building custom Docker images
- Using Docker networks and volumes
Continue experimenting with Docker and explore other advanced topics like multi-stage builds, container orchestration with Kubernetes, and CI/CD integration.
Challenge Exercise
Python API with Persistent Data
Create a multi-container application with these components:
- A Python Flask API server that stores data in a persistent volume
- Connect containers using a custom Docker network
- Configure the containers to start automatically when Docker starts
Here's a starter for your Flask API (app.py):
from flask import Flask, request, jsonify
import os
import json
import socket
app = Flask(__name__)
DATA_FILE = '/data/items.json'
# Create data directory if it doesn't exist
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
# Initialize with empty data if file doesn't exist
if not os.path.exists(DATA_FILE):
with open(DATA_FILE, 'w') as f:
json.dump([], f)
@app.route('/items', methods=['GET'])
def get_items():
with open(DATA_FILE, 'r') as f:
return jsonify(json.load(f))
@app.route('/items', methods=['POST'])
def add_item():
new_item = request.json
with open(DATA_FILE, 'r') as f:
items = json.load(f)
items.append(new_item)
with open(DATA_FILE, 'w') as f:
json.dump(items, f)
return jsonify({"status": "success", "message": "Item added"})
@app.route('/info', methods=['GET'])
def get_info():
return jsonify({
"hostname": socket.gethostname(),
"items_count": len(json.load(open(DATA_FILE, 'r'))),
"container": True
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)Docker Lab with Go
This lab provides practical exercises using Go to reinforce the Docker concepts covered in the lecture.
Prerequisites
- Docker Engine installed on your system
- Basic command line familiarity
- Text editor of your choice
Exercise 1: Getting Started with Docker
1.1 Verify Docker Installation
Ensure Docker is properly installed on your system:
docker --version
docker info1.2 Running Your First Container
Let's run an interactive container using the official Alpine image:
docker run -it --rm alpine shInside the container, try some commands:
ls
cat /etc/os-release
apk updateType exit to leave the container.
Exercise 2: Building a Go Application with Docker
2.1 Create a Simple Go Web Server
Create a directory for your project:
mkdir docker-go-lab
cd docker-go-labCreate a file named main.go:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type ServerInfo struct {
Hostname string `json:"hostname"`
GoVersion string `json:"go_version"`
Container bool `json:"container"`
}
func main() {
// Get hostname
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown"
}
// Define HTTP handlers
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Hello from Docker!\n")
fmt.Fprintf(w, "This is a Go application running in a container.\n")
fmt.Fprintf(w, "Container hostname: %s\n", hostname)
})
http.HandleFunc("/api/info", func(w http.ResponseWriter, r *http.Request) {
info := ServerInfo{
Hostname: hostname,
GoVersion: "1.20", // Hardcoded for simplicity, in reality use runtime.Version()
Container: true,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
})
// Start server
port := 8080
fmt.Printf("Starting server on port %d...\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}2.2 Create a Go Module
Initialize a Go module:
go mod init docker-go-lab2.3 Create a Dockerfile
Create a file named Dockerfile:
# Build stage
FROM golang:1.20-alpine AS build
WORKDIR /app
# Copy go module files first for better caching
COPY go.mod ./
# Copy source code
COPY main.go ./
# Build the application
RUN go build -o server .
# Runtime stage
FROM alpine:latest
WORKDIR /app
# Copy the binary from the build stage
COPY --from=build /app/server /app/server
# Expose the port
EXPOSE 8080
# Run the application
CMD ["/app/server"]2.4 Build and Run Your Image
Build your Docker image:
docker build -t go-web-app .Run the container:
docker run -d -p 8080:8080 --name go-app go-web-appAccess your application:
# Using curl
curl http://localhost:8080
# Using curl for JSON API
curl http://localhost:8080/api/infoYou can also visit http://localhost:8080 in your browser.
2.5 Explore Your Container
Check the running containers:
docker psView container logs:
docker logs go-appExecute commands in the running container:
docker exec -it go-app shStop and remove your container:
docker stop go-app
docker rm go-appExercise 3: Multi-Stage Builds and Optimization
3.1 Create an Optimized Dockerfile
Let's create an even more optimized Dockerfile that produces a smaller image:
# Build stage
FROM golang:1.20-alpine AS build
WORKDIR /app
# Copy go module files
COPY go.mod ./
COPY main.go ./
# Build with optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o server .
# Runtime stage - using scratch (empty) image
FROM scratch
# Copy SSL certificates for HTTPS requests
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
WORKDIR /app
# Copy the binary from the build stage
COPY --from=build /app/server /app/server
# Expose the port
EXPOSE 8080
# Run the application
CMD ["/app/server"]Build and tag this optimized image:
docker build -t go-web-app:optimized -f Dockerfile.optimized .Compare the image sizes:
docker images | grep go-web-appExercise 4: Docker Networks and Volumes with Go
4.1 Create a Stateful Go Application
Create a new file named stateful.go:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"sync"
)
// Counter represents a simple counter with mutex for concurrent access
type Counter struct {
Value int `json:"value"`
Path string `json:"-"`
mu sync.Mutex
}
// NewCounter creates a new counter and loads its value from file if available
func NewCounter(dataPath string) *Counter {
c := &Counter{
Value: 0,
Path: dataPath,
}
// Create directory if it doesn't exist
dir := filepath.Dir(dataPath)
if err := os.MkdirAll(dir, 0755); err != nil {
log.Printf("Error creating directory: %v", err)
}
// Try to load existing counter value
if data, err := ioutil.ReadFile(dataPath); err == nil {
var storedCounter Counter
if err := json.Unmarshal(data, &storedCounter); err == nil {
c.Value = storedCounter.Value
}
}
return c
}
// Increment increases the counter and saves it
func (c *Counter) Increment() int {
c.mu.Lock()
defer c.mu.Unlock()
c.Value++
// Save to file
data, err := json.Marshal(c)
if err != nil {
log.Printf("Error marshaling counter: %v", err)
return c.Value
}
if err := ioutil.WriteFile(c.Path, data, 0644); err != nil {
log.Printf("Error writing counter: %v", err)
}
return c.Value
}
// GetValue returns the current value
func (c *Counter) GetValue() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.Value
}
func main() {
// Create a counter that persists to a file
counter := NewCounter("/data/counter.json")
// Get hostname for display
hostname, _ := os.Hostname()
// Define HTTP handlers
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Hello from Docker!\n")
fmt.Fprintf(w, "This is a stateful Go application with persistence.\n")
fmt.Fprintf(w, "Container hostname: %s\n", hostname)
fmt.Fprintf(w, "Current count: %d\n", counter.GetValue())
fmt.Fprintf(w, "Visit /increment to increase the counter.\n")
})
http.HandleFunc("/increment", func(w http.ResponseWriter, r *http.Request) {
newValue := counter.Increment()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"value": newValue,
"hostname": hostname,
})
})
// Start server
port := 8080
fmt.Printf("Starting server on port %d with data persistence...\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}Create a Dockerfile for the stateful application:
FROM golang:1.20-alpine AS build
WORKDIR /app
COPY stateful.go .
RUN go build -o stateful-app stateful.go
FROM alpine:latest
WORKDIR /app
COPY --from=build /app/stateful-app .
# Create a volume mount point
VOLUME /data
EXPOSE 8080
CMD ["/app/stateful-app"]4.2 Run with Persistent Volume
Build the stateful application:
docker build -t go-stateful-app -f Dockerfile.stateful .Create a volume:
docker volume create go-app-dataRun the container with the volume:
docker run -d -p 8080:8080 -v go-app-data:/data --name stateful-app go-stateful-appTest the persistence:
- Visit
http://localhost:8080/incrementseveral times - Stop and remove the container
- Start a new container with the same volume
- Verify the counter value persisted
# Stop and remove the container
docker stop stateful-app
docker rm stateful-app
# Start a new container with the same volume
docker run -d -p 8080:8080 -v go-app-data:/data --name stateful-app-2 go-stateful-app
# Check if the counter value persisted
curl http://localhost:8080Conclusion
This lab has covered:
- Running containers with Docker
- Building Go applications for containerized environments
- Using multi-stage builds for optimization
- Working with Docker volumes for persistence
These practices are essential for developing modern, containerized Go applications.
Challenge Exercise
Build a multi-container application with these components:
- A Go API server that handles requests
- A separate data storage container
- Connect them using Docker networks for communication
- Implement proper volume mounts for data persistence
Both containers should be configured to restart automatically if they crash or if Docker restarts.
Docker Command Cheat Sheet
This cheat sheet provides a quick reference for commonly used Docker commands.
Docker System Commands
| Command | Description |
|---|---|
docker version | Display Docker version information |
docker info | Display system-wide information |
docker system prune | Remove all unused containers, networks, images, and build cache |
docker system df | Show Docker disk usage |
Docker Image Commands
| Command | Description |
|---|---|
docker images | List all images |
docker pull <image-name> | Pull an image from registry |
docker build -t <name:tag> <path> | Build an image from Dockerfile |
docker rmi <image-id> | Remove an image |
docker image prune | Remove unused images |
docker tag <source-image> <target-image> | Create a tag for image |
docker history <image-name> | Show history of an image |
docker save -o <file.tar> <image-name> | Save image to a tar archive |
docker load -i <file.tar> | Load image from a tar archive |
docker inspect <image-id> | Display detailed information about an image |
Docker Container Commands
| Command | Description |
|---|---|
docker ps | List running containers |
docker ps -a | List all containers (including stopped) |
docker run <image-name> | Run a container |
docker run -it <image-name> <command> | Run container interactively |
docker run -d <image-name> | Run container in detached mode |
docker run -p <host-port>:<container-port> <image-name> | Run container with port mapping |
docker run -v <host-path>:<container-path> <image-name> | Run container with volume mount |
docker run --name <container-name> <image-name> | Run container with a specific name |
docker run --rm <image-name> | Run container and remove it when it exits |
docker stop <container-id> | Stop a running container |
docker start <container-id> | Start a stopped container |
docker restart <container-id> | Restart a container |
docker rm <container-id> | Remove a container |
docker rm -f <container-id> | Force remove a running container |
docker exec -it <container-id> <command> | Execute a command in a running container |
docker logs <container-id> | Fetch the logs of a container |
docker logs -f <container-id> | Follow log output of a container |
docker cp <container-id>:<container-path> <host-path> | Copy files from container to host |
docker cp <host-path> <container-id>:<container-path> | Copy files from host to container |
docker stats | Display live container resource usage |
docker inspect <container-id> | Display detailed information about a container |
docker top <container-id> | Display the running processes of a container |
docker update --memory <limit> <container-id> | Update container resources |
Docker Network Commands
| Command | Description |
|---|---|
docker network ls | List all networks |
docker network create <network-name> | Create a network |
docker network rm <network-name> | Remove a network |
docker network inspect <network-name> | Display detailed information about a network |
docker network connect <network-name> <container-id> | Connect a container to a network |
docker network disconnect <network-name> <container-id> | Disconnect a container from a network |
docker network prune | Remove all unused networks |
Docker Volume Commands
| Command | Description |
|---|---|
docker volume ls | List all volumes |
docker volume create <volume-name> | Create a volume |
docker volume rm <volume-name> | Remove a volume |
docker volume inspect <volume-name> | Display detailed information about a volume |
docker volume prune | Remove all unused volumes |
Dockerfile Instructions
| Instruction | Description |
|---|---|
FROM | Set base image |
WORKDIR | Set working directory |
COPY | Copy files from host to container |
ADD | Copy files from host or URL to container |
RUN | Execute command during build |
ENV | Set environment variable |
ARG | Define build-time variable |
EXPOSE | Expose port |
VOLUME | Create mount point |
CMD | Default command for container |
ENTRYPOINT | Configure container executable |
LABEL | Add metadata to image |
USER | Set user for container |
HEALTHCHECK | Check container health |
Common Docker Run Options
| Option | Description |
|---|---|
-d, --detach | Run container in background |
-e, --env | Set environment variables |
-p, --publish | Publish container's port to the host |
-v, --volume | Bind mount a volume |
--name | Assign a name to the container |
--network | Connect to a network |
--restart | Restart policy (no, on-failure, always, unless-stopped) |
--rm | Remove container when it exits |
-it | Interactive mode with TTY |
--memory | Memory limit |
--cpus | Number of CPUs |
Docker Command Examples
Running a Nginx Web Server
docker run -d -p 8080:80 --name my-nginx nginxBuilding a Custom Image
docker build -t my-app:1.0 .Running a Container with Environment Variables
docker run -d -p 3000:3000 -e NODE_ENV=production --name my-node-app my-node-appCreating a Docker Network and Connecting Containers
docker network create my-network
docker run -d --name db --network my-network mongo
docker run -d --name app --network my-network -p 8000:8000 my-appVolume Mounting for Persistence
docker run -d -p 27017:27017 -v mongodb-data:/data/db --name mongodb mongoRunning a MySQL Container with Environment Variables
docker run -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=secretpassword \
-e MYSQL_DATABASE=mydb \
-e MYSQL_USER=user \
-e MYSQL_PASSWORD=password \
-v mysql-data:/var/lib/mysql \
--name mysql \
mysql:8.0Docker Best Practices
This document outlines recommended practices for effectively developing, deploying, and managing Docker containers in production environments.
Image Building Best Practices
Use Specific Base Image Tags
Always use specific version tags for base images rather than latest to ensure consistency and reproducibility.
# Bad practice
FROM node:latest
# Good practice
FROM node:18.16.0-slimKeep Images Small
- Use lightweight base images (Alpine, slim variants)
- Minimize layers by combining related commands
- Remove unnecessary files in the same layer they were created
# Bad practice
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
RUN pip install requests
# Good practice
FROM python:3.11-alpine
RUN pip install --no-cache-dir requestsUse Multi-Stage Builds
Use multi-stage builds to separate build-time dependencies from runtime dependencies, resulting in smaller final images.
# Build stage
FROM node:18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["npm", "start"]Leverage Build Cache Effectively
Order Dockerfile instructions from least to most frequently changing to optimize build cache usage.
# Optimal order for caching
FROM node:18-alpine
WORKDIR /app
# Rarely changes
COPY package*.json ./
RUN npm ci
# More frequently changes
COPY . .
RUN npm run build
CMD ["npm", "start"]Avoid Running as Root
Run containers with a non-root user for security.
FROM node:18-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]Container Runtime Best Practices
Use Environment Variables for Configuration
Externalize configuration using environment variables.
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# Run with: docker run -e DB_HOST=mongodb -e API_KEY=12345 my-appSet Health Checks
Define health checks to enable automated monitoring and container replacement.
FROM nginx:alpine
COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost/ || exit 1Implement Proper Logging
Configure applications to log to stdout/stderr for integration with Docker's logging system.
# Application should log to stdout/stderr
# Then view logs with: docker logs container_nameDefine Resource Limits
Always set resource constraints for containers in production.
# Set memory and CPU limits
docker run --memory=512m --cpus=0.5 my-applicationUse Volumes for Persistent Data
Use named volumes for data that needs to persist beyond container lifecycle.
# Create a named volume
docker volume create my-data
# Use the volume
docker run -v my-data:/app/data my-applicationSecurity Best Practices
Scan Images for Vulnerabilities
Regularly scan your Docker images for known vulnerabilities.
# Example using Trivy
trivy image myapp:1.0Sign and Verify Images
Sign your images and verify signatures before deployment for supply chain security.
# Sign image
docker trust sign myapp:1.0
# Only run verified images
docker trust inspect --pretty myapp:1.0Keep Base Images Updated
Regularly update base images to include security patches.
# Pull latest version of the specific tag you use
docker pull node:18-alpineLimit Capabilities
Run containers with minimal required Linux capabilities.
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-appUse Read-Only Filesystem
Mount the container filesystem as read-only when possible.
docker run --read-only my-applicationDevelopment Workflow Best Practices
Use Docker for Local Development
Create development-specific Dockerfiles for your application.
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]Run with:
docker build -f Dockerfile.dev -t myapp-dev .
docker run -v $(pwd)/src:/app/src -p 3000:3000 myapp-devImplement CI/CD for Docker Images
Automate building, testing, and deploying Docker images.
# Example GitHub Actions workflow
name: Docker Build and Push
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and test
run: |
docker build -t myapp:test .
docker run myapp:test npm test
- name: Push to registry
if: success()
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag myapp:test myorg/myapp:${{ github.sha }}
docker push myorg/myapp:${{ github.sha }}Use Docker BuildKit
Enable BuildKit for faster, more efficient builds.
# Enable BuildKit
export DOCKER_BUILDKIT=1
docker build .Monitoring and Maintenance
Set Up Container Monitoring
Implement monitoring for all Docker containers in production.
# Example using Prometheus and cAdvisor
docker run -d \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--name=cadvisor \
gcr.io/cadvisor/cadvisor:latestLog Aggregation
Implement centralized logging for all containers.
# Run ELK stack for log aggregation
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-e "discovery.type=single-node" \
elasticsearch:7.14.0
docker run -d \
--name logstash \
--link elasticsearch \
-v "$PWD/logstash.conf:/usr/share/logstash/pipeline/logstash.conf" \
logstash:7.14.0
docker run -d \
--name kibana \
--link elasticsearch \
-p 5601:5601 \
kibana:7.14.0Regular Cleanup
Implement regular cleanup of unused Docker objects.
# Create a cleanup cron job
0 0 * * * docker system prune -af --volumes > /var/log/docker-cleanup.log 2>&1Production Deployment Considerations
Use Orchestration Tools
For production environments with multiple containers, use orchestration tools like Kubernetes or Docker Swarm.
Implement Rolling Updates
Configure services for zero-downtime updates.
# Docker Swarm example
docker service create --name myapp --replicas 3 myapp:1.0
docker service update --image myapp:1.1 myappBackup Strategies
Implement regular backups for all persistent data.
# Example backup script for a volume
docker run --rm -v my_volume:/data -v $(pwd)/backup:/backup alpine \
tar -czf /backup/my_volume_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .Conclusion
Following these best practices will help you build more secure, efficient, and maintainable Docker-based applications. Remember that Docker is constantly evolving, so it's important to stay updated with the latest recommendations and security advisories.