Skip to content

Docker Essentials

Docker allows you to package applications with their dependencies into lightweight, portable containers. Here’s everything you need to get started.

Why Docker?

  • Consistency — “It works on my machine” becomes irrelevant
  • Isolation — Each service runs in its own container
  • Portability — Deploy the same image anywhere
  • Scalability — Spin up multiple instances effortlessly

Installing Docker

# macOS (via Homebrew)
brew install --cask docker

# Verify installation
docker --version
docker compose version

Your First Dockerfile

Create a Dockerfile in your project root:

# Use an official Node.js runtime
FROM node:20-alpine

# Set working directory
WORKDIR /app

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

# Copy application code
COPY . .

# Expose port
EXPOSE 3000

# Start the application
CMD ["node", "server.js"]

Essential Commands

# Build an image
docker build -t my-app .

# Run a container
docker run -d -p 3000:3000 --name my-app my-app

# View running containers
docker ps

# View logs
docker logs my-app

# Stop and remove
docker stop my-app
docker rm my-app

Docker Compose

For multi-service applications, use docker-compose.yml:

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://db:5432/myapp
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
# Start all services
docker compose up -d

# Stop all services
docker compose down

Best Practices

  1. Use multi-stage builds to reduce image size
  2. Never store secrets in images — use environment variables
  3. Use .dockerignore to exclude unnecessary files
  4. Pin specific versions of base images (node:20-alpine, not node:latest)
  5. One process per container — keep containers focused