Spring Boot #

Spring Boot adalah framework Java paling populer untuk membangun aplikasi enterprise. Docker Compose memungkinkan developer menjalankan seluruh stack Spring Boot (aplikasi, database, cache, message broker) dengan satu perintah, tanpa harus install Java, Maven, atau service backend secara manual.

Artikel ini membahas setup lengkap Docker Compose untuk local development Spring Boot, dari Dockerfile multi-stage hingga docker-compose.yml dengan service dependency.

Prasyarat #

Pastikan sudah terinstall:

  • Docker dan Docker Compose versi terbaru
  • Java 21+ (opsional, untuk development di host tanpa Docker)
  • Maven atau Gradle (untuk build management)

Project Spring Boot standar dengan struktur Maven:

my-spring-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   └── resources/
│   └── test/
├── pom.xml
└── mvnw

Dockerfile Multi-Stage #

Untuk Spring Boot, Dockerfile multi-stage penting agar image akhir sekecil mungkin. Stage pertama untuk build dengan semua dependencies Maven, stage kedua hanya berisi JAR yang sudah di-build.

# syntax=docker/dockerfile:1.6
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# Copy dependency descriptors dulu untuk cache layer
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN chmod +x mvnw && ./mvnw dependency:go-offline

# Copy source code dan build
COPY src ./src
RUN ./mvnw package -DskipTests

# Extract layers untuk optimasi image (Spring Boot 2.3+)
RUN mkdir -p target/dependency && \
    cd target/dependency && \
    jar -xf ../*.jar

# Stage kedua: runtime image
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

# Copy layered JAR
COPY --from=builder /app/target/dependency/BOOT-INF/lib /app/lib
COPY --from=builder /app/target/dependency/BOOT-INF/classes /app/classes
COPY --from=builder /app/target/dependency/META-INF /app/META-INF

# Jalankan sebagai non-root
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

EXPOSE 8080

ENTRYPOINT ["java", "-cp", "classes:lib/*", "com.example.myapp.Application"]

Pattern layered JAR (diperkenalkan di Spring Boot 2.3) memungkinkan Docker cache setiap layer dependencies secara terpisah. Perubahan pada kode aplikasi tidak invalidate layer dependencies, sehingga build ulang sangat cepat.

docker-compose.yml untuk Development #

# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: builder  # pakai stage builder untuk development
    image: my-spring-app:dev
    container_name: myapp
    command: ./mvnw spring-boot:run
    volumes:
      - ./src:/app/src
      - ./pom.xml:/app/pom.xml
      - target:/app/target  # Maven build cache
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/myapp
      - SPRING_DATASOURCE_USERNAME=app
      - SPRING_DATASOURCE_PASSWORD=dev
      - SPRING_REDIS_HOST=cache
      - SPRING_REDIS_PORT=6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=myapp
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    ports:
      - "6379:6379"

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"

volumes:
  db-data:
  target:

Penjelasan Service #

app — service utama Spring Boot. Pakai stage builder (yang punya Maven) untuk development. command: ./mvnw spring-boot:run menjalankan aplikasi dengan Maven. Volume bind mount source code + pom.xml agar perubahan source code terlihat langsung (Maven DevTools juga akan auto-restart).

db — PostgreSQL untuk data persisten. Healthcheck pg_isready agar dependent tahu kapan database siap.

cache — Redis untuk caching dan session. Healthcheck redis-cli ping.

mailhog — mock SMTP server untuk testing email. Tidak perlu healthcheck.

Hot Reload dengan Spring DevTools #

Spring Boot DevTools menyediakan auto-restart saat classpath berubah. Untuk development container, tambahkan dependency dan set environment.

pom.xml:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
  </dependency>
</dependencies>

application-dev.yml:

spring:
  devtools:
    livereload:
      enabled: true
    restart:
      enabled: true

Saat kamu edit file Java dan save, DevTools detect perubahan dan restart aplikasi dalam container. Untuk IDE-based workflow dengan live reload, tambahkan juga spring-boot-devtools di dependencies.

Database Migration dengan Flyway atau Liquibase #

Untuk production, schema migration harus otomatis. Flyway adalah pilihan populer untuk Spring Boot.

pom.xml:

<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>

db/migration/V1__create_users.sql:

CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE,
  name VARCHAR(255) NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

db/migration/V2__create_orders.sql:

CREATE TABLE orders (
  id BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(id),
  total_amount DECIMAL(10, 2) NOT NULL,
  status VARCHAR(50) NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Flyway otomatis mendeteksi file SQL di db/migration dan run saat startup. Untuk development, kamu bisa reset database dengan menghapus volume:

docker compose down -v  # hapus db-data
docker compose up -d

Testing dengan Testcontainers #

Testcontainers adalah library Java untuk integration test dengan container. Sangat berguna untuk testing Spring Boot di environment yang terisolasi.

pom.xml:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>postgresql</artifactId>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>

Test class:

@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {

  @Container
  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
      .withDatabaseName("test")
      .withUsername("test")
      .withPassword("test");

  @DynamicPropertySource
  static void configure(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
  }

  @Autowired
  private UserRepository userRepository;

  @Test
  void shouldSaveUser() {
    User user = new User("[email protected]", "Test User");
    userRepository.save(user);
    assertThat(userRepository.findAll()).hasSize(1);
  }
}

Testcontainers otomatis pull image, jalankan container, dan inject konfigurasi. Bersih setelah test selesai.

Build dan Run #

# Build image
docker compose build

# Jalankan semua service
docker compose up -d

# Lihat log aplikasi
docker compose logs -f app

# Stop semua
docker compose down

Akses:

  • Aplikasi: http://localhost:8080
  • PostgreSQL: localhost:5432 (user app, password dev)
  • Redis: localhost:6379
  • MailHog UI: http://localhost:8025

Profile Environment #

Spring Boot punya konsep profile untuk konfigurasi per environment.

application.yml (default):

spring:
  application:
    name: my-spring-app

application-dev.yml (development):

spring:
  datasource:
    url: jdbc:postgresql://db:5432/myapp
  jpa:
    show-sql: true
logging:
  level:
    com.example.myapp: DEBUG

application-prod.yml (production):

spring:
  datasource:
    url: ${DATABASE_URL}
  jpa:
    show-sql: false
logging:
  level:
    root: WARN

Aktifkan profile via SPRING_PROFILES_ACTIVE environment variable atau --spring.profiles.active=dev argumen.

Multi-Service dengan Spring Cloud #

Untuk aplikasi microservice, setup Compose dengan multiple Spring Boot apps.

services:
  gateway:
    build: ./gateway
    ports:
      - "8080:8080"
    depends_on:
      - auth-service
      - user-service
  
  auth-service:
    build: ./auth-service
    depends_on:
      - db
  
  user-service:
    build: ./user-service
    depends_on:
      - db
      - cache
  
  order-service:
    build: ./order-service
    depends_on:
      - db
      - mq
  
  db:
    image: postgres:16-alpine
    # ...
  
  cache:
    image: redis:7-alpine
  
  mq:
    image: rabbitmq:3-management

Tiap service punya Dockerfile dan application.yml sendiri. Compose orchestrate startup.

Best Practice #

Pakai Profile untuk Environment #

Jangan hardcode konfigurasi. Pakai Spring profile untuk dev/prod/test yang berbeda.

Database Migration Tool #

Flyway atau Liquibase untuk schema migration. Versi migration file (V1__, V2__, dst.) sesuai urutan.

Healthcheck untuk Service Dependent #

Database, Redis, dan message broker harus punya healthcheck. Spring Boot bisa akses health endpoint external untuk monitoring.

Image Layer Spring Boot #

Pakai layered JAR untuk image yang optimal. Dependencies di layer terpisah dari kode aplikasi.

Resource Limits #

services:
  app:
    deploy:
      resources:
        limits:
          memory: 1G

JVM bisa makan memory signifikan, set limit yang sesuai.

Logging ke File atau External #

Default Spring Boot log ke stdout. Untuk production, configure ke file atau external logging driver.

logging:
  file:
    name: /var/log/app.log

Troubleshooting #

Port Already in Use #

Error: bind: address already in use

Cek proses yang pakai port 8080:

lsof -i :8080  # Mac/Linux
netstat -ano | findstr :8080  # Windows

Stop proses atau ubah port mapping di Compose.

Maven Build Lambat #

Build Maven di dalam container lambat karena download dependencies. Cache layer dengan target volume:

volumes:
  - target:/app/target

Hot Reload Tidak Jalan #

Pastikan DevTools dependency ada, profile dev aktif, dan bind mount source code benar. Cek log untuk error.

Database Connection Refused #

Pastikan db healthy sebelum app start. Gunakan healthcheck + depends_on: condition: service_healthy.

Spring Boot 3 dengan Native Build #

Spring Boot 3 mendukung GraalVM native image untuk startup super cepat dan memory footprint kecil.

pom.xml:

<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
</plugin>

Dockerfile untuk native build:

FROM ghcr.io/graalvm/graalvm-ce:java17-22.3.0 AS builder

WORKDIR /app
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN chmod +x mvnw && ./mvnw dependency:go-offline

COPY src ./src
RUN ./mvnw -Pnative native:compile

FROM alpine:3.19
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY --from=builder /app/target/myapp /app/myapp
EXPOSE 8080
ENTRYPOINT ["/app/myapp"]

Native image Spring Boot startup dalam ~50ms (vs ~3 detik untuk JVM), memory footprint ~50MB (vs ~200MB untuk JVM).

Build lebih lama (menit), tapi runtime sangat efisien — cocok untuk serverless dan Kubernetes.

Spring Boot Actuator #

Actuator menyediakan endpoint untuk monitoring dan management.

pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true
  metrics:
    export:
      prometheus:
        enabled: true

Endpoint penting:

  • /actuator/health — status kesehatan
  • /actuator/info — info aplikasi
  • /actuator/metrics — metrics
  • /actuator/prometheus — metrics format Prometheus
  • /actuator/env — environment variables

OpenTelemetry Distributed Tracing #

Untuk observability microservices, Spring Boot 3 mendukung OpenTelemetry out of the box.

pom.xml:

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>

<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

application.yml:

management:
  tracing:
    sampling:
      probability: 1.0
  otlp:
    tracing:
      endpoint: http://jaeger:4318/v1/traces

Setiap request ke service akan di-trace, dengan span ID yang diteruskan ke service lain. Berguna untuk debug latency di arsitektur microservice.

Reactive Spring Boot dengan WebFlux #

Untuk aplikasi reactive, Spring Boot WebFlux adalah pilihannya.

pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
@RestController
public class UserController {
  
  private final UserRepository userRepository;
  
  @GetMapping("/users")
  public Flux<User> listUsers() {
    return userRepository.findAll();
  }
  
  @GetMapping("/users/{id}")
  public Mono<User> getUser(@PathVariable Long id) {
    return userRepository.findById(id);
  }
}

WebFlux berjalan di Netty (bukan Tomcat). Untuk Docker, image base perlu support Netty:

FROM eclipse-temurin:21-jre-alpine
# WebFlux + Netty di image ini jalan tanpa issue

Service Discovery dengan Eureka #

Untuk arsitektur microservice dengan Spring Cloud, Eureka adalah service registry default.

docker-compose.yml:

services:
  eureka:
    image: springcloud/eureka-server
    ports:
      - "8761:8761"
  
  user-service:
    build: ./user-service
    environment:
      - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka:8761/eureka/
    depends_on:
      - eureka

Setiap microservice register ke Eureka saat start, dan lookup service lain via Eureka.

Distributed Tracing dengan Zipkin #

Zipkin adalah tool populer untuk distributed tracing.

docker-compose.yml:

services:
  zipkin:
    image: openzipkin/zipkin
    ports:
      - "9411:9411"
  
  app:
    build: .
    environment:
      - SPRING_ZIPKIN_BASE_URL=http://zipkin:9411

Spring Boot otomatis mengirim trace ke Zipkin. UI di http://localhost:9411.

Recap Cheatsheet #

Pattern Tool
Build & runtime Multi-stage Dockerfile + layered JAR
Hot reload Spring DevTools + bind mount
Schema migration Flyway / Liquibase
Integration test Testcontainers
Health Spring Actuator
Tracing OpenTelemetry / Micrometer
Service discovery Spring Cloud Eureka
Reactive Spring WebFlux
Native GraalVM native image
API Gateway Spring Cloud Gateway
Config server Spring Cloud Config

Ringkasan #

  • Spring Boot ideal untuk local development — Postgres, Redis, dan service lain berjalan sebagai container.
  • Dockerfile multi-stage dengan layered JAR untuk image optimal. Builder stage untuk build, runtime stage untuk eksekusi.
  • Hot reload dengan Spring DevTools + bind mount source code. Perubahan source code trigger auto-restart.
  • Database migration dengan Flyway atau Liquibase. File SQL di db/migration dengan versi V1__, V2__, dst.
  • Testcontainers untuk integration test di environment terisolasi tanpa mock.
  • Spring profile untuk konfigurasi per environment (dev, prod, test). Aktifkan via SPRING_PROFILES_ACTIVE.
  • Healthcheck untuk service dependent (Postgres pg_isready, Redis redis-cli ping).
  • Resource limits penting untuk JVM — set memory yang cukup untuk heap dan metaspace.
  • Best practice: layered JAR, profile per environment, schema migration tool, hot reload, resource limits, logging configuration.
  • Troubleshooting: cek port conflict, Maven build cache, hot reload config, database healthcheck.
  • Multi-service dengan Spring Cloud: tiap service punya Dockerfile, Compose orchestrate startup, gateway sebagai entry point.
  • Production readiness dengan profile terpisah, image multi-stage, healthcheck, dan resource limits.

← Sebelumnya: Best Practice Docker Compose   Berikutnya: Quarkus →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact