What Docker Solves (And What It Doesn't)

Docker solves one of the most persistent headaches in software deployment: "it works on my machine." By packaging an application together with its dependencies, runtime, and configuration into a portable container, Docker eliminates environment inconsistencies between development, staging, and production.

What Docker doesn't solve is cluster orchestration, persistent storage complexity, or networking at scale — that's where Kubernetes comes in. But for a huge proportion of workloads, Docker alone is more than sufficient.

Core Concepts You Need to Understand

  • Image: A read-only blueprint for a container. Images are built from a Dockerfile and stored in registries like Docker Hub.
  • Container: A running instance of an image. Lightweight, isolated, and ephemeral by default.
  • Dockerfile: A text file containing build instructions for creating an image.
  • Registry: A storage and distribution system for Docker images (e.g., Docker Hub, GitHub Container Registry, AWS ECR).
  • Volume: A mechanism for persisting data outside the container lifecycle.
  • Network: Docker's virtual networking that allows containers to communicate with each other and the outside world.

Installing Docker on Ubuntu

sudo apt update
sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Log out and back in after adding yourself to the docker group.

Essential Docker Commands

Command What It Does
docker run -d -p 80:80 nginx Run Nginx in the background, map port 80
docker ps List running containers
docker ps -a List all containers (including stopped)
docker logs container_id View container output logs
docker exec -it container_id bash Open a shell inside a running container
docker stop container_id Gracefully stop a container
docker rm container_id Remove a stopped container
docker images List local images
docker rmi image_name Delete a local image

Writing Your First Dockerfile

Here's a minimal Dockerfile for a Node.js application:

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

Build and run it with:

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

Using Docker Compose for Multi-Container Apps

Docker Compose lets you define and run multi-container applications with a single YAML file. Here's an example with a web app and database:

services:
  web:
    image: my-app:latest
    ports:
      - "80:3000"
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Start everything with docker compose up -d. Stop with docker compose down.

Key Best Practices

  • Use specific image tags (e.g., node:20-alpine), not latest, for reproducibility
  • Keep images small — use Alpine-based images and multi-stage builds
  • Never store secrets in Dockerfiles or images; use environment variables or Docker secrets
  • Use named volumes for persistent data; never rely on container filesystems
  • Regularly clean up unused images and containers with docker system prune

Next Steps

Once you're comfortable with Docker basics, explore Docker Compose networking, health checks, and resource limits. After that, Kubernetes becomes a natural progression for managing containers at scale across multiple hosts.