What Is Docker and Why Should You Care?
Docker is a platform that packages your application and all its dependencies into a container — a lightweight, portable, self-sufficient unit. The famous promise: "it works on my machine" becomes "it works everywhere."
Before Docker, deploying apps meant wrestling with environment differences between development, staging, and production. With Docker, you ship the environment alongside the app.
Core Concepts
Image — A read-only blueprint. Like a class in OOP. Container — A running instance of an image. Like an object. Dockerfile — A script of instructions to build an image. Registry — A storage hub for images (Docker Hub, GitHub Container Registry).
Installing Docker
Download Docker Desktop from docker.com for macOS or Windows. On Linux:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Verify:
docker --version
docker run hello-world
Your First Dockerfile
Let's containerize a simple Node.js Express app.
app.js:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello from Docker!'));
app.listen(3000);
Dockerfile:
# Base image
FROM node:20-alpine
# Set working directory inside container
WORKDIR /app
# Copy package files first (layer caching)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy rest of the app
COPY . .
# Expose port
EXPOSE 3000
# Run the app
CMD ["node", "app.js"]
Build and run:
docker build -t my-node-app .
docker run -p 3000:3000 my-node-app
Visit http://localhost:3000 — your app is running in a container.
Essential Docker Commands
docker images # list images
docker ps # running containers
docker ps -a # all containers (including stopped)
docker stop <id> # stop a container
docker rm <id> # remove container
docker rmi <image> # remove image
docker logs <id> # view container logs
docker exec -it <id> sh # shell into running container
Docker Compose — Multi-Container Apps
Most real apps need multiple services: a web server, a database, a cache. docker-compose orchestrates them together.
docker-compose.yml:
version: '3.9'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
depends_on:
- db
db:
image: postgres:16-alpine
volumes:
- pg_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
pg_data:
docker compose up -d # start all services in background
docker compose logs -f # tail logs from all services
docker compose down # stop and remove containers
.dockerignore
Always add a .dockerignore to prevent bloating your image:
node_modules
.git
.env
*.log
Multi-Stage Builds (Production Best Practice)
Reduce image size dramatically by only including what's needed in the final image:
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Conclusion
Docker is one of those tools that changes how you think about software. Once you containerize your first real app, you'll never want to go back to manual environment setup. Start with the Dockerfile, graduate to docker-compose, and you'll cover 90% of what most developers need day-to-day.