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, passworddev) - 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/migrationdengan versiV1__,V2__, dst.- Testcontainers untuk integration test di environment terisolasi tanpa mock.
- Spring profile untuk konfigurasi per environment (
dev,prod,test). Aktifkan viaSPRING_PROFILES_ACTIVE.- Healthcheck untuk service dependent (Postgres
pg_isready, Redisredis-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 →