Docker in 20 Minutes: Everything You Need to Start Containerizing
Stop saying "it works on my machine." Here's Docker, explained simply.
What Problem Does Docker Solve?
WITHOUT Docker:
Your app: "Works on my laptop! 🎉"
Server: "Doesn work. Node version wrong. Library missing. OS different." 💀
WITH Docker:
Your app: "Here's a box with everything inside. Run this box." 📦
Server: "Running the box. Works perfectly." ✅
Docker packages your app + dependencies into a container that runs identically everywhere.
Docker vs VM (Quick)
Virtual Machine:
┌─────────────────────────────────────┐
│ Host Operating System │
│ ┌───────────────────────────────┐ │
│ │ Guest OS (full Linux) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Your App │ │ │
│ │ │ + Dependencies │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
Heavy: Full OS per VM = GBs of space, minutes to boot
Docker Container:
┌─────────────────────────────────────┐
│ Host Operating System │
│ ┌───────────────────────────────┐ │
│ │ Docker Engine │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ App1│ │ App2│ │ App3│ │ │
│ │ └─────┘ └─────┘ └─────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
Light: Shared kernel = MBs, seconds to start
The 3 Core Concepts
1. Dockerfile = Recipe
# Dockerfile — instructions to build your container image
# Base image (start from something that already exists)
FROM node:22-alpine
# Set working directory
WORKDIR /app
# Copy package files first (layer caching!)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Expose port
EXPOSE 3000
# Command to run
CMD ["node", "server.js"]
2. Image = Built snapshot (the recipe cooked)
# Build an image from Dockerfile
docker build -t myapp:v1 .
# List images
docker images
# Remove image
docker rmi myapp:v1
3. Container = Running instance (image brought to life)
# Run a container from an image
docker run -d -p 3000:3000 --name myapp myapp:v1
# List running containers
docker ps
# Stop container
docker stop myapp
# Start stopped container
docker start myapp
# Remove container
docker rm myapp
Your First Dockerized App
Step 1: Create the files
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Hello from Docker!',
time: new Date().toISOString(),
hostname: require('os').hostname()
}));
});
server.listen(3000, () => console.log('Running on :3000'));
// package.json
{
"name": "docker-demo",
"version": "1.0.0",
"main": "server.js"
}
# Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
# .dockerignore (like .gitignore for Docker)
node_modules
Dockerfile
.git
.env
npm-debug.log
Step 2: Build and Run
# Build the image
docker build -t docker-demo .
# Run it!
docker run -d -p 3000:3000 docker-demo
# Test it
curl http://localhost:3000
# {"message":"Hello from Docker!","time":"...","hostname":"abc123"}
# It's running in a container! 🎉
Essential Docker Commands Cheat Sheet
| Command | Description |
|---|---|
docker build -t name . |
Build image from Dockerfile |
docker run -d -p 8080:80 name |
Run container in background |
docker ps |
List running containers |
docker ps -a |
All containers (including stopped) |
docker logs <name> |
View container logs |
docker exec -it <name> sh |
Enter running container |
docker stop <name> |
Stop container |
docker rm <name> |
Remove container |
docker images |
List images |
docker rmi <image> |
Remove image |
docker system prune |
Remove all unused data |
Multi-Container Apps: Docker Compose
When you need multiple services (app + database + redis):
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
# Start all services
docker compose up -d
# View logs of all services
docker compose logs -f
# Logs of one service
docker compose logs -f app
# Stop everything
docker compose down
# Stop + remove volumes (fresh start)
docker compose down -v
# Rebuild after code changes
docker compose up -d --build
Production Dockerfile Tips
1. Multi-stage Builds (Smaller Images)
# Stage 1: Build
FROM node:22 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production (much smaller!)
FROM node:22-alpine AS production
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
Result: ~1GB → ~150MB image size!
2. Use .dockerignore Properly
# Always exclude these
node_modules
dist
build
.git
.gitignore
*.md
.env.env.*
!.env.example
Dockerfile
docker-compose.yml
.npmrc
coverage
.vscode
.idea
3. Non-Root User (Security!)
FROM node:22-alpine
# Create non-root user
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
4. Health Check
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
Common Docker Problems & Solutions
"I changed my code but nothing happened!"
# Docker builds use layer caching. Force rebuild:
docker build --no-cache -t myapp .
# Or with Compose:
docker compose up -d --build
"My container exits immediately!"
# Check why:
docker logs <container-name>
# Common causes:
# 1. Process crashes → fix the error in logs
# 2. No foreground process → use CMD or entrypoint
# 3. Port conflict → change port mapping
"How do I debug inside a container?"
# Shell into a running container
docker exec -it <container-name> sh
# Check file system
ls -la /app
# Check installed packages
cat /etc/os-release
node -v
npm -v
# Test commands manually
node -e "console.log(require('./package.json'))"
"Disk space full!"
# Clean up Docker
docker system prune -af --volumes
# Check what's using space
docker system df
Docker vs Alternatives
| Tool | Best For | Learning Curve |
|---|---|---|
| Docker | General containerization | Medium |
| Podman | Rootless containers (security) | Low (drop-in) |
| Kubernetes | Orchestration at scale | High |
| Railway/Render | Zero-config deployment | Very low |
Start with Docker. Add Kubernetes when you have 5+ services.
Are you using Docker yet? What's your biggest question about containers?
Follow @armorbreak for more DevOps content.
Top comments (0)