
by Zelkulon19 March 20251 min read
Using Docker and Docker Compose correctly for microservices: Multistage Builds, Service Discovery, Health Checks and a complete local development environment.
Target Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β docker-compose.yml β β β β ββββββββββββ ββββββββββββ ββββββββββββ β β β Next.js β β Order β β Payment β β β β :3000 β β Service β β Service β β β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β β βββββββββββββββββ΄ββββββββββββββββ β β Bridge-Network β β ββββββββββββ ββββββββββββ ββββββββββββ β β βPostgreSQLβ β RabbitMQ β β Redis β β β β :5432 β β :5672 β β :6379 β β β ββββββββββββ ββββββββββββ ββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Multistage Dockerfile (Spring Boot)
# βββ Stage 1: Build ββββββββββββββββββββββββββββββββββββββββββββ
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -B # Layer cache for dependencies
COPY src src
RUN ./mvnw package -DskipTests -B
RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/layers
# βββ Stage 2: Runtime (~150 MB instead of ~600 MB) ββββββββββββββββββ
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
COPY --from=build /app/target/layers/dependencies/ ./
COPY --from=build /app/target/layers/spring-boot-loader/ ./
COPY --from=build /app/target/layers/application/ ./
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8081
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s CMD wget -qO- http://localhost:8081/actuator/health || exit 1
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]Docker Compose
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASS:-secret}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
rabbitmq:
image: rabbitmq:3.13-management-alpine
ports:
- "5672:5672"
- "15672:15672" # Management UI
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 30s
order-service:
build: ./order-service
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/orderdb
SPRING_RABBITMQ_HOST: rabbitmq
ports:
- "8081:8081"
depends_on:
postgres:
condition: service_healthy # Only start when DB is ready!
rabbitmq:
condition: service_healthy
restart: unless-stopped
volumes:
postgres_data:Useful Commands
# Start all services
docker compose up -d
# Infrastructure only (for local development)
docker compose up -d postgres rabbitmq redis
# Logs of a service
docker compose logs -f order-service
# Rebuild service
docker compose up -d --build order-service
# Status of all services
docker compose ps
# Open container shell
docker compose exec order-service sh
# Stop everything + delete volumes (reset database data)
docker compose down -vStartup Sequence with Health Checks
1. Start postgres, rabbitmq, redis
β
βΌ
2. Health checks (pg_isready, rabbit ping, redis ping)
β all healthy
βΌ
3. Start order-service, payment-service
β
βΌ
4. Health checks (actuator/health)
β all healthy
βΌ
5. Frontend starts
Without condition: service_healthy β Spring starts before
PostgreSQL is ready β connection error!Summary
- Consistency: "Works on my machine" is a thing of the past
- Isolation: Services run without mutual dependencies
- Fast onboarding: docker compose up β done
- Production parity: Local environment matches deployment exactly
- Scalability: docker compose up --scale order-service=3