Menu
AgiCAD Home
DevOps April 18, 2026 · 13 min read

Docker for Web Developers: A Practical Introduction

Containers, images, Docker Compose, and how to stop saying "it works on my machine" — a ground-up guide for developers who have heard about Docker but never made it click.

Every web developer has experienced the scenario: your application runs perfectly locally, you deploy it to a colleague's machine or a staging server, and something breaks. A different Python version. A library that compiled differently on Linux vs macOS. A system dependency that was installed manually months ago and never documented. Docker does not merely reduce this problem — it eliminates it, by packaging your application and every one of its dependencies into a self-contained unit that runs identically everywhere.

This guide covers everything you need to start using Docker practically in web development: what containers actually are, how images work, writing Dockerfiles, and orchestrating multi-service applications with Docker Compose. No prior Docker experience assumed.

Containers vs. Virtual Machines

Before Docker, the standard solution for environment consistency was virtual machines. A VM emulates an entire computer — hardware, BIOS, operating system, and all. It works, but it is slow to start (minutes), heavy on resources (gigabytes of RAM per instance), and cumbersome to share.

A container takes a different approach. Rather than emulating hardware and running a separate OS kernel, containers share the host OS kernel but isolate the user space — the filesystem, processes, network, and environment variables. The result is a unit that starts in seconds, uses tens of megabytes rather than gigabytes, and can run dozens simultaneously on a modest laptop.

Core Concepts: Images and Containers

An image is a read-only snapshot of a filesystem — a blueprint containing the OS files, runtime, application code, installed dependencies, and configuration. Images are built from a Dockerfile and stored in registries like Docker Hub.

A container is a running instance of an image. When you start a container, Docker adds a thin writable layer on top of the read-only image. The image stays unchanged; all runtime changes happen in that writable layer.

Your First Dockerfile

A Dockerfile is a text file containing instructions for building an image, executed sequentially. Here is a minimal example for a Python Flask application:

# Use an official Python base image
FROM python:3.12-slim

WORKDIR /app

# Copy dependency file first (enables layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000
CMD ["python", "app.py"]

Build with docker build -t my-flask-app . and run with docker run -p 5000:5000 my-flask-app.

Layer Caching: The Key to Fast Builds

Copying requirements.txt and installing dependencies before copying application code is intentional. Docker caches layers — if a layer's inputs have not changed since the last build, Docker reuses the cached result. By separating the slow pip install step from application code changes, you ensure rebuilds are fast when only Python files change.

Essential Docker Commands

# Build an image
docker build -t image-name:tag .

# Run a container (detached, with port mapping)
docker run -d -p 8000:8000 image-name

# List running containers
docker ps

# View and follow logs
docker logs -f container-name

# Execute a shell inside a running container
docker exec -it container-name bash

# Stop and remove a container
docker stop container-name && docker rm container-name

# Housekeeping
docker system prune

Docker Compose: Multi-Service Applications

Most real web applications need more than one service — a web app, a database, a cache. Docker Compose defines and runs all of them together with a single docker-compose.yml file.

version: '3.9'

services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password

  redis:
    image: redis:7-alpine

volumes:
  postgres_data:

docker compose up starts all services, wires them on a shared network, and makes each accessible by service name as hostname. Your Django app reaches the database at db:5432 and Redis at redis:6379.

Multi-Stage Builds for Smaller Production Images

# Stage 1: Build
FROM node:20 AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production — only compiled output
FROM nginx:alpine
COPY --from=builder /build/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

The final image contains only nginx and your compiled static files — not Node.js, npm, or source code. This typically reduces production image size by 60–80%.

Common Mistakes to Avoid

  • Secrets in images: Never put API keys in a Dockerfile using ENV — they are visible in image layers. Pass secrets at runtime or use a secrets manager.
  • Running as root: Add USER appuser in production Dockerfiles. Root in a container can mean root on the host if the container is compromised.
  • Missing .dockerignore: Without it, your entire node_modules directory (hundreds of MB) and .env files get sent to the Docker daemon on every build. Always add a .dockerignore.
  • Using latest tags in production: Pin specific versions — FROM python:3.12.3-slim, not FROM python:latest. Unpinned base images can silently introduce breaking changes.

Docker in the AgiCAD Workflow

At AgiCAD, Docker is the standard for all client project environments. Every repository includes a docker-compose.yml for local development — new developer onboarding takes under five minutes regardless of OS. Staging and production environments mirror the local configuration with overrides, eliminating the entire class of bugs that exist purely in environment gaps.

Next Steps

With the fundamentals in place, natural next areas to explore include: Docker health checks for production-ready service dependencies, Docker networking for advanced inter-container communication, and CI/CD integration to automatically build and push images on every commit. The Docker official documentation is excellent once the core mental model is established.

Need Help Containerising Your Application?

AgiCAD sets up production-ready Docker environments and CI/CD pipelines. Get in touch to discuss your project.

Talk to Us