AWS Docker

Docker Compose for Full-Stack Apps:
App + DB + Redis in One File

By Ankit Kumar, Founder of Kyvronix Technologies · 13 min read

The days of manually installing PostgreSQL, Redis, and your Node/Python environment on your local machine (and dealing with "it works on my machine" bugs) are long gone. Docker Compose allows you to define your entire infrastructure—backend, database, caching layer—as code.

In this guide, we will build the ultimate docker-compose.yml file that we use at Kyvronix to spin up full-stack development and production environments in a single command.

1. Anatomy of a Docker Compose File

Docker Compose uses a YAML file to define multiple "services". Each service is a container. When you run docker-compose up, Compose creates an isolated virtual network where these containers can talk to each other using their service names as hostnames.

2. The Complete docker-compose.yml

Here is a production-ready template that runs a Node.js API, PostgreSQL database, and Redis cache. It includes health checks, persistent volumes, and secure environment variable injection.

version: '3.8'

services:
  # 1. The API Application
  api:
    build:
      context: .
      dockerfile: Dockerfile
    image: my-fullstack-api:latest
    container_name: api_service
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres       # Notice we use the service name!
      - DB_PORT=5432
      - DB_USER=${DB_USER}     # Pull from .env file
      - DB_PASSWORD=${DB_PASS}
      - REDIS_HOST=redis
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # 2. PostgreSQL Database
  postgres:
    image: postgres:15-alpine
    container_name: pg_db
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: myapp_db
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d myapp_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # 3. Redis Cache
  redis:
    image: redis:7-alpine
    container_name: redis_cache
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

volumes:
  pgdata:
  redisdata:
  

3. Key Concepts Explained

Internal DNS

Notice how the API connects to Postgres: DB_HOST=postgres. Docker provides automatic DNS resolution. The API container doesn't need to know the IP address of the database container; it simply connects to the hostname postgres.

depends_on & Health Checks

Your API will likely crash if it tries to connect to the database before the database is ready. The depends_on block, combined with condition: service_healthy, ensures Docker waits until PostgreSQL actually responds to pg_isready before starting the API container.

Volumes for Persistence

Containers are ephemeral. If you delete a container, its data is destroyed. The volumes block mounts a directory from your host machine into the container, ensuring that your database rows and Redis keys survive container restarts and updates.

Conclusion

By defining your infrastructure in a single docker-compose.yml file, you make onboarding new developers as easy as running docker-compose up, and deploying to a fresh EC2 instance becomes completely reproducible.

— Ankit Kumar