← deck$Dhruv Verma~/writing
applied security & backend
$cat ~/writing/docker-what-and-why.mdEssay

Jun 15, 2026

Docker — What It Is and Why It Exists

A from-first-principles tour of Docker — containers vs. VMs, images, Dockerfiles, volumes, Compose, and the workflow that ends 'it works on my machine' for good.

The Problem It Solves

You've written some code. It works perfectly on your laptop. You hand it off to a teammate, and it crashes immediately. They have a different version of Python, a different OS, a missing library. Sound familiar? This is the classic "it works on my machine" problem — and it's been plaguing software teams for decades.

Docker exists to kill that problem dead.

So What Is Docker?

Docker is a tool that lets you package your application together with everything it needs to run — the code, the runtime, the libraries, the configuration — into a single portable unit called a container. You ship the container, not just the code. Wherever that container runs, the app behaves identically.

Think of it like this: instead of mailing someone a recipe and hoping they have the right ingredients, you mail them the fully prepped meal in a sealed box. They just heat it up. Same result, every time.

Containers vs. Virtual Machines

This is the most important concept to get right early, because Docker often gets confused with virtual machines (VMs). They both isolate software, but in fundamentally different ways.

Virtual MachineDocker Container
What it virtualizesThe entire hardwareJust the OS process layer
IncludesFull guest OS (Windows, Linux, etc.)Only the app + its dependencies
SizeGigabytesMegabytes
Startup timeMinutesSeconds (often milliseconds)
Resource usageHeavyVery lightweight
Isolation levelFull OS isolationProcess-level isolation

A VM says: "I'll pretend to be an entirely separate computer." A container says: "I'll just wall off my little corner of this computer."

Both share the physical hardware, but containers also share the host machine's OS kernel rather than booting their own OS. This is why they're so much lighter and faster.

The Core Building Blocks

Image

A Docker image is the blueprint. It's a read-only, layered snapshot of everything your app needs — OS base, dependencies, your code, start-up commands. Images don't run; they're the instructions for building something that does.

You can think of an image like a class in object-oriented programming. It defines the structure, but isn't a live instance yet.

Container

A container is a running instance of an image. Start the same image ten times, you get ten isolated containers. This is the object to the image's class.

Containers are:

Dockerfile

A Dockerfile is a plain text file where you write instructions to build your image. It's the recipe. Here's a minimal real example:

FROM python:3.11-slim          # Start from an official Python base image
WORKDIR /app                   # Set the working directory inside the container
COPY requirements.txt .        # Copy your dependency list
RUN pip install -r requirements.txt  # Install dependencies
COPY . .                       # Copy the rest of your code
CMD ["python", "app.py"]       # The command to run when the container starts

Each line in a Dockerfile creates a new layer in the image. Docker is smart about caching these layers — if you only change your app code, it won't reinstall dependencies from scratch. It reuses the cached layers and only rebuilds what changed.

Docker Registry

Once you build an image, you need somewhere to store it so others (or other servers) can pull it. That's a registry. Docker Hub is the public default — it's like GitHub but for images. Private registries exist too (AWS ECR, Google Container Registry, etc.).

The workflow looks like:

Write Dockerfile → docker build → Image → docker push → Registry ↓ docker pull → Container runs anywhere

Essential Commands

You don't need to memorize every flag, but these are the ones you'll use constantly:

# Build an image from a Dockerfile in the current directory
docker build -t my-app:1.0 .

# Run a container from an image
docker run my-app:1.0

# Run in the background (detached mode)
docker run -d my-app:1.0

# Map a port: host_port:container_port
# (so your browser can reach the app running inside the container)
docker run -p 8080:3000 my-app:1.0

# List running containers
docker ps

# List all containers (including stopped ones)
docker ps -a

# Stop a running container
docker stop <container_id>

# Remove a container
docker rm <container_id>

# List all local images
docker images

# Remove an image
docker rmi my-app:1.0

# Pull an image from Docker Hub
docker pull nginx

# Open a shell inside a running container (extremely useful for debugging)
docker exec -it <container_id> bash

Volumes — Persisting Data

Containers are ephemeral by design. If you write data inside a container and it stops, that data vanishes. Volumes are how you break that rule intentionally.

A volume is a link between a folder on your host machine and a folder inside the container. Changes in one are reflected in the other.

# Mount a host folder into the container
docker run -v /my/local/folder:/app/data my-app:1.0

This is essential for things like databases running in containers — you don't want your data wiped every restart.

Docker Compose — Running Multiple Containers Together

Real apps rarely have just one piece. You might have a web server, a database, and a caching layer. Spinning each up manually with docker run gets painful fast.

Docker Compose lets you define your entire multi-container setup in a single docker-compose.yml file:

version: "3"
services:
  web:
    build: .
    ports:
      - "8080:3000"
    depends_on:
      - db

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

volumes:
  db_data:

Then you just run:

docker compose up      # Start everything
docker compose down    # Stop and clean up

One command. Everything starts together, wired up, ready to go.

How It All Fits Together

Here's the full mental model end-to-end:

  1. You write your app code
  2. You write a Dockerfile describing how to package it
  3. docker build reads the Dockerfile and produces an image
  4. docker run takes that image and spins up a live container
  5. You docker push the image to a registry
  6. Anyone, anywhere pulls that image and runs the exact same container

The same container you tested on your laptop ships to staging, then to production, without a single "but it worked for me" conversation.

Why This Matters in Practice

ready · open to security & backend rolesutf-8 · writing.md