Docker for Developers: Images, Containers, and Compose

DevOps2026-10-07TryQuickToolBox

You've heard about Docker and containers, but when you try to use it, you're overwhelmed by terms like images, containers, volumes, and Compose. Or maybe you're already using Docker but your images are huge, builds are slow, and managing multiple services feels like a chore. This guide cuts through the noise. We'll cover the essentials of Docker for developers, focusing on what you actually need to know to build, run, and manage containerized applications efficiently.

Why Docker? The Problem It Solves

Before Docker, deploying an app meant matching the exact environment: specific OS versions, libraries, and configurations. "It works on my machine" became a cliché because it was true. Docker solves this by packaging your application with its dependencies into a single, portable unit called a container. Containers are lightweight, isolated, and run consistently anywhere Docker is installed.

Images vs. Containers: The Core Distinction

An image is a read-only template that contains your application code, runtime, libraries, and settings. A container is a running instance of an image. Think of an image as a class and a container as an object. You can run multiple containers from the same image, each isolated from the others.

How Images Are Built

Images are built from a Dockerfile, a text file with instructions. Here's a minimal example for a Node.js app:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Each instruction creates a layer. Layers are cached, so if you change only your source code, Docker reuses the cached layers for dependencies, making rebuilds fast.

Building and Running

Build the image and run a container:

docker build -t my-app .
docker run -p 3000:3000 my-app

The -p flag maps port 3000 on your host to port 3000 in the container.

Best Practices for Efficient Docker Images

Large images slow down builds, pushes, and deployments. Follow these practices to keep images lean:

Here's a multi-stage Dockerfile for a Go application:

# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/main .

# Final stage
FROM alpine:latest
COPY --from=builder /app/main /main
CMD ["/main"]

The final image contains only the compiled binary and Alpine, not the Go toolchain.

Managing Data with Volumes

Containers are ephemeral; when they're removed, their data is lost. Volumes persist data outside the container lifecycle. Use them for databases, uploads, or any state that must survive restarts.

docker run -v my-data:/var/lib/postgresql/data postgres

This creates a named volume my-data that Docker manages. For development, you can also bind-mount your source code into the container for live reloading:

docker run -v $(pwd):/app -p 3000:3000 my-app

Docker Compose: Orchestrating Multiple Containers

Real applications often need multiple services: a web server, a database, a cache. Starting each with docker run is tedious and error-prone. Docker Compose lets you define and run multi-container apps with a single YAML file.

A Practical docker-compose.yml

Here's a Compose file for a Node.js app with PostgreSQL and Redis:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
  cache:
    image: redis:7-alpine
volumes:
  postgres_data:

Run docker compose up to start all services. Compose creates a default network so containers can reach each other by service name (e.g., db, cache).

Common Compose Commands

When to Use Docker Compose vs. Kubernetes

Docker Compose is ideal for local development, testing, and small deployments. Kubernetes excels at orchestrating containers across clusters for production at scale. For many developers, Compose is sufficient for day-to-day work, and you can graduate to Kubernetes when you need auto-scaling, self-healing, and advanced networking.

FeatureDocker ComposeKubernetes
Use caseLocal dev, small deploymentsProduction clusters
ComplexityLowHigh
ScalingManualAutomatic
Learning curveGentleSteep

FAQ

What's the difference between an image and a container?

An image is a read-only template with your app and dependencies. A container is a running instance of an image. You can run many containers from one image.

How do I reduce Docker image size?

Use smaller base images (like Alpine), multi-stage builds, combine RUN commands, and use a .dockerignore file to exclude unnecessary files.

Can I use Docker Compose in production?

Yes, for small-scale deployments. For larger, dynamic environments, Kubernetes or Docker Swarm is more suitable.

Ready to optimize your Docker workflow? Check out our Nginx Log Analyzer to debug and monitor your containerized web servers.