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), notlatest, 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.