Skip to content

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:

Docker ​

📚 Table of Contents ​

  1. What is Docker?
  2. Evolution of Docker: From Linux Kernel to Container Revolution
  3. Containers, Images, and Registries
  4. Why Docker Matters in the Software Development Life Cycle (SDLC)
  5. Docker vs Virtual Machines: A Technical Comparison
  6. How Docker Uses the OS Kernel: Namespaces & cgroups
  7. User Space vs Kernel Space in Docker
  8. Writing a Simple Dockerfile
  9. 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 + Configs

Each 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.

bash
# 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 ​

FeatureVirtual MachineDocker Container
Boot TimeMinutesSeconds
OS RequirementsFull Guest OS per VMShares Host OS Kernel
SizeGBsMBs
PerformanceSlower (Hypervisor overhead)Near-native
PortabilityLimitedHigh (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
└── Dockerfile

app.py ​

python
print("Hello from inside Docker!")

Dockerfile ​

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 ​

bash
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:

bash
docker --version
docker info

1.2 Running Your First Container ​

Let's run an interactive container using the official Ubuntu image:

bash
docker run -it --rm ubuntu bash

Inside the container, try some commands:

bash
ls
cat /etc/os-release
apt update

Type exit to leave the container.

Exercise 2: Building Custom Images ​

2.1 Create a Simple Python Web Application ​

Create a directory for your project:

bash
mkdir docker-lab
cd docker-lab

Create a file named app.py:

python
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:

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:

bash
docker build -t my-python-app .

Run the container:

bash
docker run -d -p 3000:3000 --name web-app my-python-app

Test your application:

bash
# Using curl for plain text response
curl http://localhost:3000

# Using curl for JSON API
curl http://localhost:3000/api/info

2.4 Explore Your Container ​

Check the running containers:

bash
docker ps

View container logs:

bash
docker logs web-app

Execute commands in the running container:

bash
docker exec -it web-app bash

Stop and remove your container:

bash
docker stop web-app
docker rm web-app

Exercise 3: Advanced Docker Concepts ​

3.1 Docker Networks ​

Create a custom bridge network:

bash
docker network create mynetwork

Run containers on the network:

bash
docker run -d --name container1 --network mynetwork alpine sleep infinity
docker run -d --name container2 --network mynetwork alpine sleep infinity

Test container communication:

bash
docker exec container1 ping -c 3 container2

List networks and inspect:

bash
docker network ls
docker network inspect mynetwork

Clean up:

bash
docker stop container1 container2
docker rm container1 container2
docker network rm mynetwork

3.2 Docker Volumes ​

Create a named volume:

bash
docker volume create mydata

Run a container with the volume:

bash
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:

bash
docker exec vol-test cat /data/dates.txt

Use a different container to access the same volume:

bash
docker run --rm -v mydata:/data alpine cat /data/dates.txt

Clean up:

bash
docker stop vol-test
docker rm vol-test
docker volume rm mydata

Conclusion ​

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:

  1. A Python Flask API server that stores data in a persistent volume
  2. Connect containers using a custom Docker network
  3. Configure the containers to start automatically when Docker starts

Here's a starter for your Flask API (app.py):

python
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:

bash
docker --version
docker info

1.2 Running Your First Container ​

Let's run an interactive container using the official Alpine image:

bash
docker run -it --rm alpine sh

Inside the container, try some commands:

bash
ls
cat /etc/os-release
apk update

Type 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:

bash
mkdir docker-go-lab
cd docker-go-lab

Create a file named main.go:

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:

bash
go mod init docker-go-lab

2.3 Create a Dockerfile ​

Create a file named Dockerfile:

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:

bash
docker build -t go-web-app .

Run the container:

bash
docker run -d -p 8080:8080 --name go-app go-web-app

Access your application:

bash
# Using curl
curl http://localhost:8080

# Using curl for JSON API
curl http://localhost:8080/api/info

You can also visit http://localhost:8080 in your browser.

2.5 Explore Your Container ​

Check the running containers:

bash
docker ps

View container logs:

bash
docker logs go-app

Execute commands in the running container:

bash
docker exec -it go-app sh

Stop and remove your container:

bash
docker stop go-app
docker rm go-app

Exercise 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:

dockerfile
# 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:

bash
docker build -t go-web-app:optimized -f Dockerfile.optimized .

Compare the image sizes:

bash
docker images | grep go-web-app

Exercise 4: Docker Networks and Volumes with Go ​

4.1 Create a Stateful Go Application ​

Create a new file named stateful.go:

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:

dockerfile
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:

bash
docker build -t go-stateful-app -f Dockerfile.stateful .

Create a volume:

bash
docker volume create go-app-data

Run the container with the volume:

bash
docker run -d -p 8080:8080 -v go-app-data:/data --name stateful-app go-stateful-app

Test the persistence:

  1. Visit http://localhost:8080/increment several times
  2. Stop and remove the container
  3. Start a new container with the same volume
  4. Verify the counter value persisted
bash
# 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:8080

Conclusion ​

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:

  1. A Go API server that handles requests
  2. A separate data storage container
  3. Connect them using Docker networks for communication
  4. 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 ​

CommandDescription
docker versionDisplay Docker version information
docker infoDisplay system-wide information
docker system pruneRemove all unused containers, networks, images, and build cache
docker system dfShow Docker disk usage

Docker Image Commands ​

CommandDescription
docker imagesList 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 pruneRemove 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 ​

CommandDescription
docker psList running containers
docker ps -aList 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 statsDisplay 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 ​

CommandDescription
docker network lsList 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 pruneRemove all unused networks

Docker Volume Commands ​

CommandDescription
docker volume lsList 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 pruneRemove all unused volumes

Dockerfile Instructions ​

InstructionDescription
FROMSet base image
WORKDIRSet working directory
COPYCopy files from host to container
ADDCopy files from host or URL to container
RUNExecute command during build
ENVSet environment variable
ARGDefine build-time variable
EXPOSEExpose port
VOLUMECreate mount point
CMDDefault command for container
ENTRYPOINTConfigure container executable
LABELAdd metadata to image
USERSet user for container
HEALTHCHECKCheck container health

Common Docker Run Options ​

OptionDescription
-d, --detachRun container in background
-e, --envSet environment variables
-p, --publishPublish container's port to the host
-v, --volumeBind mount a volume
--nameAssign a name to the container
--networkConnect to a network
--restartRestart policy (no, on-failure, always, unless-stopped)
--rmRemove container when it exits
-itInteractive mode with TTY
--memoryMemory limit
--cpusNumber of CPUs

Docker Command Examples ​

Running a Nginx Web Server ​

bash
docker run -d -p 8080:80 --name my-nginx nginx

Building a Custom Image ​

bash
docker build -t my-app:1.0 .

Running a Container with Environment Variables ​

bash
docker run -d -p 3000:3000 -e NODE_ENV=production --name my-node-app my-node-app

Creating a Docker Network and Connecting Containers ​

bash
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-app

Volume Mounting for Persistence ​

bash
docker run -d -p 27017:27017 -v mongodb-data:/data/db --name mongodb mongo

Running a MySQL Container with Environment Variables ​

bash
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.0

Docker 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.

dockerfile
# Bad practice
FROM node:latest

# Good practice
FROM node:18.16.0-slim

Keep 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
dockerfile
# 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 requests

Use Multi-Stage Builds ​

Use multi-stage builds to separate build-time dependencies from runtime dependencies, resulting in smaller final images.

dockerfile
# 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.

dockerfile
# 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.

dockerfile
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.

dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

# Run with: docker run -e DB_HOST=mongodb -e API_KEY=12345 my-app

Set Health Checks ​

Define health checks to enable automated monitoring and container replacement.

dockerfile
FROM nginx:alpine
COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

Implement Proper Logging ​

Configure applications to log to stdout/stderr for integration with Docker's logging system.

dockerfile
# Application should log to stdout/stderr
# Then view logs with: docker logs container_name

Define Resource Limits ​

Always set resource constraints for containers in production.

bash
# Set memory and CPU limits
docker run --memory=512m --cpus=0.5 my-application

Use Volumes for Persistent Data ​

Use named volumes for data that needs to persist beyond container lifecycle.

bash
# Create a named volume
docker volume create my-data

# Use the volume
docker run -v my-data:/app/data my-application

Security Best Practices ​

Scan Images for Vulnerabilities ​

Regularly scan your Docker images for known vulnerabilities.

bash
# Example using Trivy
trivy image myapp:1.0

Sign and Verify Images ​

Sign your images and verify signatures before deployment for supply chain security.

bash
# Sign image
docker trust sign myapp:1.0

# Only run verified images
docker trust inspect --pretty myapp:1.0

Keep Base Images Updated ​

Regularly update base images to include security patches.

bash
# Pull latest version of the specific tag you use
docker pull node:18-alpine

Limit Capabilities ​

Run containers with minimal required Linux capabilities.

bash
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-app

Use Read-Only Filesystem ​

Mount the container filesystem as read-only when possible.

bash
docker run --read-only my-application

Development Workflow Best Practices ​

Use Docker for Local Development ​

Create development-specific Dockerfiles for your application.

dockerfile
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

Run with:

bash
docker build -f Dockerfile.dev -t myapp-dev .
docker run -v $(pwd)/src:/app/src -p 3000:3000 myapp-dev

Implement CI/CD for Docker Images ​

Automate building, testing, and deploying Docker images.

yaml
# 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.

bash
# Enable BuildKit
export DOCKER_BUILDKIT=1
docker build .

Monitoring and Maintenance ​

Set Up Container Monitoring ​

Implement monitoring for all Docker containers in production.

bash
# 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:latest

Log Aggregation ​

Implement centralized logging for all containers.

bash
# 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.0

Regular Cleanup ​

Implement regular cleanup of unused Docker objects.

bash
# Create a cleanup cron job
0 0 * * * docker system prune -af --volumes > /var/log/docker-cleanup.log 2>&1

Production 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.

bash
# Docker Swarm example
docker service create --name myapp --replicas 3 myapp:1.0
docker service update --image myapp:1.1 myapp

Backup Strategies ​

Implement regular backups for all persistent data.

bash
# 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.

Built with VitePress · Notes © 2026 [Your Name]