DEV Community

Lucas M Dev
Lucas M Dev

Posted on

Docker for Developers: The Practical Guide You Actually Need

Docker clicked for me the day I stopped thinking "containers" and started thinking "portable environments."

Here's everything you need to go from zero to productive.

What is Docker, actually?

Docker packages your app + its dependencies into a container — like a lightweight VM, but faster and more portable.

Without Docker:  "It works on my machine!"
With Docker:     "It works in any machine!"
Enter fullscreen mode Exit fullscreen mode

Install Docker

# macOS / Windows: Install Docker Desktop
# https://www.docker.com/products/docker-desktop/

# Linux (Ubuntu)
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
Enter fullscreen mode Exit fullscreen mode

Your First Container

# Run nginx web server
docker run -p 8080:80 nginx

# Open http://localhost:8080 — it works!
# Ctrl+C to stop

# Run in background
docker run -d -p 8080:80 --name my-nginx nginx

# Check what's running
docker ps

# Stop it
docker stop my-nginx
Enter fullscreen mode Exit fullscreen mode

The Dockerfile

A Dockerfile is a recipe for your container image.

# Node.js app example
FROM node:20-alpine

WORKDIR /app

# Copy package files first (for layer caching)
COPY package*.json ./
RUN npm ci --production

# Copy app code
COPY . .

# Expose port
EXPOSE 3000

# Start command
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Build and run:

docker build -t my-app .
docker run -p 3000:3000 my-app
Enter fullscreen mode Exit fullscreen mode

Essential Docker Commands

# Images
docker images                    # List images
docker pull node:20-alpine       # Download an image
docker rmi image-name            # Remove image
docker build -t name:tag .       # Build from Dockerfile

# Containers
docker ps                        # Running containers
docker ps -a                     # All containers (including stopped)
docker run -it ubuntu bash       # Interactive mode
docker exec -it container bash   # Enter running container
docker logs container-name       # View logs
docker logs -f container-name    # Follow logs live
docker stop container-name       # Stop gracefully
docker rm container-name         # Remove container
docker rm -f container-name      # Force remove

# Volumes (persistent data)
docker run -v /local/path:/container/path image
docker run -v my-volume:/data image

# Networks
docker network ls
docker network create my-net
docker run --network my-net image

# Cleanup
docker system prune              # Remove all stopped containers, dangling images
docker system prune -a           # Remove everything unused
Enter fullscreen mode Exit fullscreen mode

Docker Compose: Multiple Containers

For apps with multiple services (app + database + cache), use docker-compose.

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    depends_on:
      - db
    volumes:
      - .:/app          # Mount code for hot reload in dev

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres-data:
Enter fullscreen mode Exit fullscreen mode
docker compose up              # Start everything
docker compose up -d           # Detached (background)
docker compose down            # Stop everything
docker compose logs -f app     # Follow app logs
docker compose exec app bash   # Enter app container
Enter fullscreen mode Exit fullscreen mode

Pro Tips

1. Layer caching — copy dependencies first

# ✅ Good: dependencies cached unless package.json changes
COPY package*.json ./
RUN npm ci
COPY . .

# 🚫 Bad: cache busted on every code change
COPY . .
RUN npm ci
Enter fullscreen mode Exit fullscreen mode

2. Use .dockerignore

# .dockerignore
node_modules
.git
.env
*.log
dist
.next
Enter fullscreen mode Exit fullscreen mode

3. Multi-stage builds for tiny images

# Build stage
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# Production stage — only what's needed
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

# Result: 200MB instead of 1GB
Enter fullscreen mode Exit fullscreen mode

4. Non-root user for security

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Enter fullscreen mode Exit fullscreen mode

5. Health checks

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:3000/health || exit 1
Enter fullscreen mode Exit fullscreen mode

Common Dockerfile Templates

Python Flask/FastAPI

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Enter fullscreen mode Exit fullscreen mode

Next.js

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Deploy Your Container

Once your image works locally:

# Push to Docker Hub
docker login
docker tag my-app username/my-app:v1.0
docker push username/my-app:v1.0

# Deploy to Railway (easiest)
# Just connect your GitHub repo — Railway auto-detects Dockerfile

# Or to VPS
ssh user@your-server
docker run -d -p 80:3000 --restart unless-stopped username/my-app:v1.0
Enter fullscreen mode Exit fullscreen mode

What made Docker "click" for you? Or what's still confusing? Drop it in the comments.

Top comments (0)