DEV Community

Dev Cookies
Dev Cookies

Posted on

πŸš€ Building & Running Multiple Services with Docker Compose

A Complete, End-to-End Guide for Modern Microservices


πŸ“– Why Docker Compose?

When you have several microservicesβ€”say a Spring Boot API, a Node.js frontend, and a PostgreSQL databaseβ€”manually building and starting containers gets messy.
Docker Compose solves this by letting you:

  • Define all services in one file (docker-compose.yml)
  • Build images automatically from local Dockerfiles
  • Create a shared network so containers talk to each other by name
  • Scale & orchestrate them with a single command

πŸ—οΈ Project Structure

A scalable layout for two Java services and a database:

multi-service-app/
β”œβ”€ service-a/
β”‚  β”œβ”€ src/...
β”‚  β”œβ”€ Dockerfile
β”‚  └─ pom.xml
β”œβ”€ service-b/
β”‚  β”œβ”€ src/...
β”‚  β”œβ”€ Dockerfile
β”‚  └─ pom.xml
β”œβ”€ database/
β”‚  └─ init.sql
β”œβ”€ .env
└─ docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Each service is independent, with its own Dockerfile and build artifacts.


🐳 Step 1: Write Service Dockerfiles

Example service-a/Dockerfile (Spring Boot JAR):

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/service-a.jar app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Enter fullscreen mode Exit fullscreen mode

Repeat similarly for service-b.

Tips

  • Use a .dockerignore file to skip target/ or node_modules/ for faster builds.
  • Pin your base images to a specific tag for reproducibility.

βš™οΈ Step 2: Create a .env File

Centralize environment variables:

POSTGRES_USER=myuser
POSTGRES_PASSWORD=secret
POSTGRES_DB=mydb
SPRING_PROFILES_ACTIVE=prod
Enter fullscreen mode Exit fullscreen mode

Docker Compose will automatically load this.


πŸ“ Step 3: The docker-compose.yml

Here’s a production-grade Compose file that:

  • Builds both microservices from source
  • Spins up a PostgreSQL database
  • Assigns explicit container names
  • Creates a dedicated bridge network
  • Configures health checks and restart policies
version: "3.9"

networks:
  app-net:
    driver: bridge

volumes:
  db-data:

services:
  service-a:
    container_name: service-a-container
    build:
      context: ./service-a
      dockerfile: Dockerfile
    image: myorg/service-a:latest
    ports:
      - "8081:8080"
    networks:
      - app-net
    environment:
      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
      DB_URL: jdbc:postgresql://db:5432/${POSTGRES_DB}
      DB_USER: ${POSTGRES_USER}
      DB_PASS: ${POSTGRES_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 5

  service-b:
    container_name: service-b-container
    build:
      context: ./service-b
      dockerfile: Dockerfile
    image: myorg/service-b:latest
    ports:
      - "8082:8080"
    networks:
      - app-net
    environment:
      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
      SERVICE_A_URL: http://service-a:8080
    depends_on:
      service-a:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 5

  db:
    container_name: postgres-container
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ Highlights

  • Networks: app-net gives each service a hostname equal to its service key (e.g., db, service-a).
  • Volumes: db-data persists database data across container restarts.
  • depends_on with healthcheck ensures dependent services wait until the database or other services are healthy.
  • Environment: pulled from .env for easy secrets management.

▢️ Step 4: Build and Launch

From the project root:

docker compose up --build -d
Enter fullscreen mode Exit fullscreen mode

This will:

  1. Build myorg/service-a and myorg/service-b images.
  2. Start three containers (service-a-container, service-b-container, postgres-container).
  3. Attach them to the app-net network.

Check everything is running:

docker ps
docker compose logs -f
Enter fullscreen mode Exit fullscreen mode

πŸ”— Service-to-Service Communication

Inside app-net:

  • service-b can call service-a at http://service-a:8080
  • Both services connect to Postgres at jdbc:postgresql://db:5432/mydb

Docker’s internal DNS means no hardcoded IP addresses.


πŸ› οΈ Useful Lifecycle Commands

Action Command
Stop containers docker compose stop
Restart with new code docker compose up --build -d
Tear down everything docker compose down
Remove images too docker compose down --rmi all
View container logs docker compose logs -f service-a
Scale a service (e.g. service-b) docker compose up -d --scale service-b=3

🧰 Best Practices & Pro Tips

  1. Multi-Stage Builds:
    Compile in a builder image, copy only the final artifact into a slim runtime image for smaller images.

  2. Secrets Management:
    For production, use Docker secrets or a vault rather than plain .env.

  3. Monitoring & Metrics:
    Integrate Prometheus/Grafana by adding services to the same network.

  4. CI/CD Integration:
    Use docker compose build in pipelines to create versioned images and push them to a registry.

  5. Resource Limits:
    Add deploy.resources.limits to control CPU and memory usage in Swarm or Compose v3+.


βœ… Recap

With this setup you can:

  • Define, build, and run multiple microservices and a database with a single command.
  • Give each container a clean name, dedicated network, and health checks.
  • Scale horizontally and manage environment configs seamlessly.

This is a one-stop, production-grade solution to bootstrap your microservice architecture with Docker Compose.
Just add more services, copy the pattern, and your stack will grow cleanly and reliably.

Top comments (0)