hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Deploying Node.js Apps with Docker

Why Docker

  • The "Works on My Machine" Problem
  • Docker Concepts

Your First Dockerfile

  • Writing a Dockerfile
  • Multi-Stage Builds
  • The .dockerignore File

Running Containers

  • Running and Managing Containers
  • Environment Variables and Secrets
  • Health Checks

Multi-Container Apps

  • Docker Compose
  • Adding a Reverse Proxy
  • Persistent Data

Deploying to Production

  • Deploying to a VPS
  • HTTPS with Let's Encrypt
  • Zero-Downtime Deploys

CI/CD

  • Building Images in CI
  • Automated Deployment

Production Hardening

  • Container Security
  • Logging and Monitoring
  • Deployment Checklist and Capstone

Docker Compose

Running multiple containers

A real app is not just one container. You have the app, a database, maybe a Redis cache, a reverse proxy. Each runs in its own container. Docker Compose defines and runs multi-container applications with a single YAML file.

docker-compose.yml

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=/data/app.db
    volumes:
      - app-data:/data
    depends_on:
      - redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    restart: unless-stopped

volumes:
  app-data:
  redis-data:

What each section does

services — Each service is a container. app is your Node.js app (built from the local Dockerfile). redis uses a pre-built image from Docker Hub.

build: . — Build the image from the Dockerfile in the current directory. Alternatively, use image: myapp:latest to pull a pre-built image.

ports — Same as docker run -p. Maps host port to container port.

environment — Same as docker run -e. Sets environment variables. For secrets, use env_file: instead.

volumes — Named volumes persist data across container restarts. app-data:/data mounts the app-data volume at /data inside the container. Your SQLite database lives here.

depends_on — Start Redis before the app. This controls startup order but does not wait for Redis to be ready (use condition: service_healthy for that).

restart — Same as docker run --restart.

healthcheck — Same as the HEALTHCHECK Dockerfile instruction, but defined in Compose.

Running with Compose

# Start all services
docker compose up -d

# View logs
docker compose logs
docker compose logs app     # Just the app
docker compose logs -f      # Follow

# Stop all services
docker compose down

# Stop and remove volumes (deletes data!)
docker compose down -v

# Rebuild after code changes
docker compose up -d --build

Networks

Docker Compose creates a network for your services automatically. Containers can reach each other by service name:

// Inside the app container, connect to Redis by service name
const redis = createClient({ url: "redis://redis:6379" });

The hostname redis resolves to the Redis container’s IP. No need to know the IP address. Docker’s DNS handles it.

Using env_file for secrets

services:
  app:
    build: .
    env_file:
      - .env.production
# .env.production
NODE_ENV=production
DATABASE_URL=/data/app.db
JWT_SECRET=your-production-secret

[!WARNING] Do not commit .env.production to git. Add it to .gitignore. On the server, create it manually or use a secrets manager.

Development vs production compose files

Use separate compose files for development and production:

# docker-compose.yml (development)
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app # Mount source code for live reload
      - /app/node_modules # Exclude node_modules
    command: npx tsx watch src/server.ts
    environment:
      - NODE_ENV=development

The development version mounts your source code into the container (volumes: - .:/app) so changes reload without rebuilding. The production version copies the code into the image at build time.

Exercises

Exercise 1: Create docker-compose.yml with your app. Run docker compose up -d. Verify the app works.

Exercise 2: Add Redis as a service. Verify the app can connect to redis://redis:6379 by service name.

Exercise 3: Stop and restart with docker compose down and docker compose up -d. Verify your data persists (it is in the named volume).

How do containers in the same Docker Compose file communicate?

← Health Checks Adding a Reverse Proxy →

© 2026 hectoday. All rights reserved.