Back to DevOpsForge Visual Builder
Production Architecture

Spring Boot Docker Compose with PostgreSQL & Maven

Updated September 2026 Java 17 / 21 LTS Multi-stage Maven Build

Building Java Spring Boot Docker containers often leads to bloated images (800MB+) if the JDK build tools remain in the final runtime. Additionally, Spring Boot needs properly mapped datasource environment variables and healthcheck endpoints to ensure zero-downtime restarts.

This production setup separates the Maven packaging phase from the lightweight OpenJDK JRE runner, connects to a tuned PostgreSQL 15 volume, and proxies through Caddy for automatic HTTPS.

Customize this Spring Boot Stack Visually

Tweak memory limits, add Redis caching, or toggle S3 cloud backups with zero manual YAML typing.

1. Production `docker-compose.yml`

Configures resource constraints and passes Spring datasource environment parameters directly to the application container.

docker-compose.yml
version: '3.8'

services:
  caddy:
    image: caddy:2-alpine
    container_name: spring_proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      backend:
        condition: service_healthy

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: spring_app
    restart: always
    expose:
      - "8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=jdbc:postgresql://database:5432/${DB_NAME:-spring_db}
      - SPRING_DATASOURCE_USERNAME=${DB_USER:-spring_user}
      - SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD:-spring_password}
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1024M
    depends_on:
      database:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 15s
      timeout: 5s
      retries: 3

  database:
    image: postgres:15-alpine
    container_name: spring_postgres
    restart: always
    environment:
      - POSTGRES_USER=${DB_USER:-spring_user}
      - POSTGRES_PASSWORD=${DB_PASSWORD:-spring_password}
      - POSTGRES_DB=${DB_NAME:-spring_db}
    volumes:
      - pg_data:/var/lib/postgresql/data
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1024M
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-spring_user} -d ${DB_NAME:-spring_db}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  caddy_data:
  caddy_config:
  pg_data:

2. Multi-Stage Maven `Dockerfile`

Caches `pom.xml` dependencies separately to avoid re-downloading Maven packages on source code edits, leaving a slim ~180MB runtime container.

backend/Dockerfile
# --- Stage 1: Build Application JAR ---
FROM maven:3.8-openjdk-17-slim AS builder
WORKDIR /app
COPY pom.xml ./
RUN mvn dependency:go-offline

COPY src ./src
RUN mvn clean package -DskipTests

# --- Stage 2: Production JRE Runtime ---
FROM openjdk:17-jdk-slim
WORKDIR /app

# Security: Run as dedicated non-root user
RUN groupadd -g 1000 spring && useradd -u 1000 -g spring spring

COPY --from=builder /app/target/*.jar app.jar
RUN chown -R spring:spring /app
USER spring

EXPOSE 8080
CMD ["java", "-jar", "app.jar"]