← All articles
CONTAINERS Local Development with Docker Compose: Databases, Qu... 2026-02-09 · 4 min read · docker-compose · postgresql · redis

Local Development with Docker Compose: Databases, Queues, and Services

Containers 2026-02-09 · 4 min read docker-compose postgresql redis local-development containers

Local Development with Docker Compose: Databases, Queues, and Services

Running PostgreSQL, Redis, and a message queue locally used to mean installing each service, managing versions, and dealing with conflicts. Docker Compose makes this trivial: define your services in a YAML file, run docker compose up, and everything starts with the right versions and configuration.

This guide provides production-tested Compose configurations for the services developers most commonly need.

The Base Pattern

# docker-compose.yml
name: my-project

services:
  postgres:
    image: postgres:17
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
  redis_data:

Key patterns:

PostgreSQL with Extensions

services:
  postgres:
    image: postgres:17
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/01-init.sql
    command: >
      postgres
        -c shared_preload_libraries=pg_stat_statements
        -c log_min_duration_statement=100
        -c log_statement=none
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5
-- docker/postgres/init.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";  -- Trigram text search

The log_min_duration_statement=100 setting logs any query taking longer than 100ms — useful for catching slow queries during development.

Redis with Persistence

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

--appendonly yes enables persistence so your cache survives restarts. allkeys-lru evicts the least recently used keys when memory is full — sensible defaults for development.

Message Queues

RabbitMQ

services:
  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"   # AMQP
      - "15672:15672" # Management UI
    environment:
      RABBITMQ_DEFAULT_USER: dev
      RABBITMQ_DEFAULT_PASS: dev
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 10s
      timeout: 10s
      retries: 5

The management UI at http://localhost:15672 lets you inspect queues, publish test messages, and monitor consumers. Login with dev/dev.

Object Storage (S3-Compatible)

MinIO

MinIO provides an S3-compatible API locally. Use it instead of connecting to real AWS S3 during development.

services:
  minio:
    image: minio/minio
    ports:
      - "9000:9000"   # S3 API
      - "9001:9001"   # Console UI
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 10s
      timeout: 5s
      retries: 5

Configure your AWS SDK to use MinIO:

import { S3Client } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  endpoint: 'http://localhost:9000',
  region: 'us-east-1',
  credentials: {
    accessKeyId: 'minioadmin',
    secretAccessKey: 'minioadmin',
  },
  forcePathStyle: true,  // Required for MinIO
});

Search

Elasticsearch

services:
  elasticsearch:
    image: elasticsearch:8.14.0
    ports:
      - "9200:9200"
    environment:
      discovery.type: single-node
      xpack.security.enabled: "false"
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\\|yellow\"'"]
      interval: 10s
      timeout: 10s
      retries: 10

  # Optional: Kibana for browsing data
  kibana:
    image: kibana:8.14.0
    ports:
      - "5601:5601"
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    depends_on:
      elasticsearch:
        condition: service_healthy

Meilisearch (Lighter Alternative)

If you need full-text search without Elasticsearch's resource overhead:

services:
  meilisearch:
    image: getmeili/meilisearch:latest
    ports:
      - "7700:7700"
    environment:
      MEILI_ENV: development
    volumes:
      - meili_data:/meili_data

Meilisearch uses 50MB of RAM vs Elasticsearch's 500MB+. For development, it's usually sufficient.

Useful Commands

# Start all services in background
docker compose up -d

# Start specific services
docker compose up -d postgres redis

# View logs (follow mode)
docker compose logs -f postgres

# Stop all services (data persists in volumes)
docker compose stop

# Stop and remove containers (data persists in volumes)
docker compose down

# Stop and remove containers AND volumes (data is deleted)
docker compose down -v

# Restart a specific service
docker compose restart postgres

# Run a one-off command in a service container
docker compose exec postgres psql -U dev -d myapp

# Check service health
docker compose ps

Tips

Use profiles for optional services

services:
  postgres:
    image: postgres:17
    # Always starts

  elasticsearch:
    image: elasticsearch:8.14.0
    profiles: [search]
    # Only starts with: docker compose --profile search up

Override files for personal settings

# docker-compose.override.yml (gitignored)
services:
  postgres:
    ports:
      - "15432:5432"  # I use a different port locally

Docker Compose automatically merges docker-compose.yml with docker-compose.override.yml. Add the override file to .gitignore so each developer can customize ports or resources without affecting the shared config.

Wait for dependencies

services:
  app:
    build: .
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

depends_on with condition: service_healthy waits until the health check passes before starting the dependent service. Without this, your app might start before the database is ready to accept connections.

Resource limits

services:
  elasticsearch:
    image: elasticsearch:8.14.0
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M

Set memory limits so a runaway service doesn't consume all your RAM during development.