Skip to content

Docker Containerization

Foreword

"It works on my machine" is the most classic developer excuse, and Docker makes that excuse disappear completely. Containerization technology packages an application and all its dependencies into a standardized unit, ensuring consistent execution across any environment. It is the cornerstone of modern software delivery.

What will you learn from this article?

After completing this chapter, you will gain:

  • Core concepts: Understand the three core concepts of images, containers, and registries
  • Architecture comparison: Understand the fundamental difference between containers and virtual machines
  • Hands-on skills: Master Dockerfile writing and common commands
  • Orchestration basics: Learn to manage multi-service applications with Docker Compose
  • Best practices: Learn about image optimization, security hardening, and other production-grade practices
ChapterContentCore Concepts
Chapter 1Why Containers Are NeededEnvironment consistency, resource efficiency, standardized delivery
Chapter 2Core ConceptsImage, Container, Registry, Dockerfile
Chapter 3Docker LifecycleWrite, Build, Push, Run, Manage
Chapter 4Docker ComposeMulti-service orchestration, networks, volumes
Chapter 5Best PracticesImage optimization, security, multi-stage builds

1. Why Containers?

Before containers, deploying an application required manually installing runtimes, configuring environment variables, and handling dependency conflicts on servers. Differences between environments (development, testing, production) were breeding grounds for bugs.

Virtual Machines vs Containers
Switch between both virtualization models to compare their architecture
App A / App B / App C
App A + Bins/Libs
App B + Bins/Libs
App C + Bins/Libs
Docker Engine
Host OS
Physical hardware
Startup speedSeconds
Resource usageShared host OS kernel (MB scale)
IsolationProcess-level isolation
DensityHundreds of containers per host
Image sizeMB scale

What Problems Do Containers Solve?

ProblemTraditional ApproachContainer Approach
Inconsistent environments"It works on my machine"Package all dependencies, consistent everywhere
Dependency conflictsApp A needs Node 14, App B needs Node 18Each container has an independent environment
Resource wasteEach VM has a full OSShare kernel, MB-level overhead
Slow deploymentManual install and configuredocker run — one command
Difficult scalingCreate new VM, install environment, deployStart new containers in seconds

The Essence of Containers

Containers are not lightweight virtual machines. Their essence is isolated processes. The Linux kernel implements containers through two mechanisms:

  • Namespaces: Isolate a process's view (PID, network, filesystem, etc.)
  • Cgroups: Limit a process's resource usage (CPU, memory, IO)

Processes inside a container are fundamentally no different from ordinary processes on the host machine — they're just "locked in a room where they can't see the outside."


2. Core Concepts

The Docker world revolves around three core concepts: Image, Container, and Registry.

ConceptAnalogyDescription
ImageClass / TemplateRead-only application template containing code, runtime, libraries, and configuration
ContainerInstance / ObjectRunning instance of an image, read-write, with independent lifecycle
RegistryApp StoreService for storing and distributing images (Docker Hub, ACR, ECR)
DockerfileRecipe / BlueprintText file defining how to build an image
VolumeExternal Hard DrivePersistent data; data survives container deletion

Image Layer Structure

Docker images are composed of multiple read-only layers stacked together. Each Dockerfile instruction creates one layer:

┌─────────────────────────┐
│  CMD ["node", "app.js"] │  ← Startup command layer
├─────────────────────────┤
│  COPY . /app            │  ← Application code layer (changes frequently)
├─────────────────────────┤
│  RUN npm install        │  ← Dependency installation layer (changes occasionally)
├─────────────────────────┤
│  FROM node:18-alpine    │  ← Base image layer (rarely changes)
└─────────────────────────┘

Why Layering Matters

Docker caches each layer. If a layer hasn't changed, the cache is reused during builds. Therefore, in Dockerfiles, you should place instructions that change infrequently at the top (like installing dependencies) and instructions that change frequently at the bottom (like copying code). This way, most builds hit the cache and are much faster.


3. Docker Lifecycle

From writing a Dockerfile to running a container, Docker's workflow is a clear pipeline.

Docker Lifecycle
Click each stage to inspect the details
📝
Write a Dockerfile
🔨
Build the image
☁️
Push to registry
▶️
Run the container
⚙️
Manage containers
Write a Dockerfile
A Dockerfile is the recipe for building an image. It starts from a base image and defines how to assemble the application environment step by step. Each instruction creates an image layer that Docker can cache for later builds.
Common commands
FROM node:18-alpineChoose the base image
WORKDIR /appSet the working directory
COPY package*.json ./Copy dependency files for cache reuse
RUN npm installInstall dependencies
COPY . .Copy application code
EXPOSE 3000Declare the port
CMD ["node", "server.js"]Start command

Dockerfile Common Instructions Reference

InstructionPurposeExample
FROMSpecify base imageFROM node:18-alpine
WORKDIRSet working directoryWORKDIR /app
COPYCopy files into imageCOPY package.json ./
RUNExecute commands during buildRUN npm install
ENVSet environment variablesENV NODE_ENV=production
EXPOSEDeclare port (documentation only)EXPOSE 3000
CMDContainer startup commandCMD ["node", "app.js"]
ENTRYPOINTContainer entry point (harder to override)ENTRYPOINT ["nginx"]

4. Docker Compose: Multi-Service Orchestration

Real projects typically involve more than one container. A web application might need: application server + database + Redis + Nginx. Docker Compose uses a single YAML file to define and manage multiple containers.

docker-compose.yml Example

yaml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=secret

  redis:
    image: redis:7-alpine

volumes:
  db-data:

Compose Core Concepts

ConceptDescriptionExample
servicesDefine individual container servicesapp, db, redis
volumesPersistent data volumesdb-data stores database files
networksCustom networks (auto-created by default)Services access each other by service name
depends_onStartup order dependenciesapp depends on db and redis
environmentEnvironment variablesDatabase password, connection address

Service Discovery

In Docker Compose, the service name is the hostname. The app container can directly access the database using db:5432 and Redis using redis:6379 without knowing IP addresses. This is thanks to Docker's built-in DNS.


5. Best Practices

5.1 Multi-stage Build

Multi-stage builds are a powerful tool for optimizing image size. The build stage installs all tools and dependencies, while the final stage only retains the files needed at runtime.

dockerfile
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

5.2 Image Optimization Checklist

OptimizationApproachEffect
Choose small base imagesUse alpine instead of ubuntuImage from ~200MB down to ~50MB
Merge RUN instructionsConnect multiple commands with &&Reduce image layers
Use .dockerignoreExclude node_modules, .git, etc.Speed up builds, reduce context
Multi-stage buildsSeparate build and runtime environmentsFinal image doesn't contain build tools
Pin version numbersnode:18.17-alpine instead of node:latestReproducible builds

5.3 Security Practices

PracticeDescription
Don't run as rootUSER node to specify a non-root user
Scan for vulnerabilitiesdocker scout or Trivy to scan images
Least privilegeOnly install necessary packages, no debug tools
Don't hardcode secretsUse environment variables or Docker Secrets
Regularly update base imagesFix security vulnerabilities promptly

Summary

Docker containerization is the infrastructure of modern software delivery, and understanding it is crucial for any developer.

Key takeaways from this chapter:

  1. Containers vs VMs: Containers share the host kernel, are lighter and faster, but slightly weaker isolation than VMs
  2. Core trio: Image (template), Container (instance), Registry (distribution)
  3. Dockerfile: Layered builds, leverage caching, place rarely-changing instructions first
  4. Docker Compose: Define multi-service applications with YAML, service names are hostnames
  5. Production practices: Multi-stage builds reduce image size, alpine base images, run as non-root

Further Reading