DevOps
Containerizing Spring Boot Applications with Docker
Complete guide to containerizing Spring Boot applications using Docker, including multi-stage builds, Docker Compose, Kubernetes deployment, and DevOps best practices.
Docker revolutionizes application deployment by packaging Spring Boot applications with their dependencies into lightweight, portable containers. This comprehensive guide covers containerizing Spring Boot applications using Docker, including creating Dockerfiles, multi-stage builds, orchestrating with Docker Compose, deploying to Kubernetes, and following DevOps best practices. With practical examples and code snippets, you'll learn how to streamline your deployment pipeline for scalability and reliability.
Why Docker?
Docker provides a consistent, portable, and efficient way to deploy Spring Boot applications, making it a cornerstone of modern DevOps practices.
Benefits
- Consistency: Ensures identical environments across development, testing, and production, eliminating "it works on my machine" issues.
- Portability: Containers run on any system with Docker, from local machines to cloud providers.
- Scalability: Easily scale applications horizontally by running multiple container instances.
- Isolation: Each container runs in its own environment, preventing conflicts between applications.
- Resource Efficiency: Containers are lightweight compared to virtual machines, optimizing resource usage.
Docker Basics
- Image: A read-only template used to create containers, containing the application and its dependencies.
- Container: A running instance of an image, isolated from the host system.
- Dockerfile: A script with instructions to build a Docker image.
- Registry: A repository (e.g., Docker Hub) for storing and distributing Docker images.
Real-World Use Case
Imagine a Spring Boot-based e-commerce application with microservices for user management, order processing, and inventory. Without Docker, deploying these services across different environments risks configuration drift. Docker ensures each service runs in a consistent environment, simplifying deployment and scaling during high-traffic events like flash sales.
Creating Dockerfiles
A Dockerfile defines the steps to build a Docker image for your Spring Boot application. Below are examples of basic and optimized Dockerfiles.
Basic Dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/myapp-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Explanation:
FROM openjdk:17-jdk-slim: Uses a lightweight OpenJDK 17 base image.WORKDIR /app: Sets the working directory inside the container.COPY target/myapp-1.0.0.jar app.jar: Copies the compiled JAR file.EXPOSE 8080: Declares the port the application uses.ENTRYPOINT ["java", "-jar", "app.jar"]: Runs the Spring Boot application.
Optimized Dockerfile
FROM openjdk:17-jdk-slim
# Create non-root user for security
RUN addgroup --system spring && adduser --system spring --ingroup spring
# Set working directory
WORKDIR /app
# Copy jar file
COPY target/myapp-1.0.0.jar app.jar
# Change ownership
RUN chown spring:spring app.jar
# Switch to non-root user
USER spring:spring
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# Run application
ENTRYPOINT ["java", "-jar", "app.jar"]Improvements:
- Runs as a non-root user (
spring) to enhance security. - Adds a health check using Spring Boot Actuator’s
/actuator/healthendpoint to monitor container health. - Properly sets ownership of the JAR file to avoid permission issues.
Multi-stage Builds
Multi-stage builds reduce image size and improve security by separating the build and runtime environments.
# Build stage
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# Runtime stage
FROM openjdk:17-jdk-slim
RUN addgroup --system spring && adduser --system spring --ingroup spring
WORKDIR /app
COPY --from=build /app/target/myapp-1.0.0.jar app.jar
RUN chown spring:spring app.jar
USER spring:spring
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]Benefits:
- Smaller Image Size: The build stage uses Maven to compile the application, but the runtime stage only includes the compiled JAR and OpenJDK.
- Security: Excludes build tools (e.g., Maven) from the final image, reducing attack surface.
- Efficiency: Skips tests during the build (
-DskipTests) to speed up the process (enable tests in CI/CD for quality assurance).
Docker Compose
Docker Compose simplifies orchestrating multiple containers, such as a Spring Boot application and a database.
Example Docker Compose Configuration
For an e-commerce application with a user service and MySQL database:
version: '3.8'
services:
user-service:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://user-db:3306/user_db
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- user-db
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 3
user-db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=user_db
ports:
- "3306:3306"
volumes:
- user-db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 3s
retries: 3
volumes:
user-db-data:Explanation:
- user-service: Builds the Spring Boot application from the Dockerfile.
- user-db: Runs a MySQL database with a persistent volume for data storage.
- Environment Variables: Configures the Spring Boot application to connect to MySQL.
- Health Checks: Ensures both services are healthy before starting dependent services.
- Volumes: Persists MySQL data to avoid data loss on container restart.
Running Docker Compose: docker-compose up -d This starts the services in detached mode. Access the application at http://localhost:8080.
Deployment Strategies
Deploying Dockerized Spring Boot applications to production requires robust strategies for scalability and reliability.
Deploying to Kubernetes
Kubernetes orchestrates containers for high availability and scalability.
Deployment YAML (for User Service):
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: myregistry/user-service:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_DATASOURCE_URL
value: "jdbc:mysql://user-db:3306/user_db"
- name: SPRING_DATASOURCE_USERNAME
value: "root"
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: password
- name: SPRING_PROFILES_ACTIVE
value: "prod"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Service YAML (for User Service):
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIPMySQL Deployment YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-db
spec:
selector:
matchLabels:
app: user-db
template:
metadata:
labels:
app: user-db
spec:
containers:
- name: user-db
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: password
- name: MYSQL_DATABASE
value: user_db
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvcPersistent Volume Claim (PVC):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiSecrets (create with kubectl):
kubectl create secret generic mysql-secrets --from-literal=password=password
Deploying to Kubernetes: kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f mysql-deployment.yaml kubectl apply -f mysql-pvc.yaml
CI/CD Integration
Integrate Docker builds into a CI/CD pipeline using GitHub Actions:
name: Build and Push Docker Image
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: myregistry/user-service:1.0.0This pipeline builds the Spring Boot application, creates a Docker image, and pushes it to a registry.
Monitoring and Logging
Monitoring and logging ensure the reliability of Dockerized applications in production.
Spring Boot Actuator
Enable Actuator for health and metrics:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>application.yml:
management:
endpoints:
web:
exposure:
include: health, metrics, prometheusPrometheus Monitoring
Scrape metrics from the /actuator/prometheus endpoint:
scrape_configs:
- job_name: 'user-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['user-service:8080']Centralized Logging with ELK Stack
Configure Logback to send logs to an ELK Stack:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.2</version>
</dependency>logback-spring.xml:
<configuration>
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>Docker Compose for ELK:
version: '3.8'
services:
logstash:
image: docker.elastic.co/logstash/logstash:8.5.0
ports:
- "5000:5000"
environment:
- xpack.monitoring.enabled=false
volumes:
- ./logstash-pipeline:/usr/share/logstash/pipeline
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
kibana:
image: docker.elastic.co/kibana/kibana:8.5.0
ports:
- "5601:5601"
depends_on:
- elasticsearchBest Practices
- Minimize Image Size:
- Use slim base images (e.g., openjdk:17-jdk-slim).
- Leverage multi-stage builds to exclude build tools.
- Example:
RUN rm -rf /app/src- Security:
- Run containers as non-root users.
- Scan images for vulnerabilities using Trivy:
trivy image myregistry/user-service:1.0.0
- Health Checks:
- Implement health checks in Dockerfiles and Kubernetes probes.
- Example:
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10- Environment Configuration:
- Use environment variables for configuration.
- Example:
env:
- name: SPRING_DATASOURCE_URL
value: "jdbc:mysql://user-db:3306/user_db"- Logging:
- Configure structured JSON logging:
@Component
public class LogService {
private static final Logger logger = LoggerFactory.getLogger(LogService.class);
public void logAction(String action) {
logger.info("{\"action\": \"{}\", \"timestamp\": \"{}\"}", action, Instant.now());
}
}- CI/CD Integration:
- Automate image building and deployment.
- Use versioned tags (e.g., myregistry/user-service:1.0.0).
- Resource Limits:
- Set CPU and memory limits in Kubernetes:
resources:
limits:
cpu: "0.5"
memory: "512Mi"
requests:
cpu: "0.2"
memory: "256Mi"Conclusion
Containerizing Spring Boot applications with Docker enables consistent, portable, and scalable deployments. By using multi-stage builds, Docker Compose, and Kubernetes, you can streamline your DevOps pipeline. Key takeaways:
- Create efficient Dockerfiles with multi-stage builds.
- Use Docker Compose for local development and testing.
- Deploy to Kubernetes for production-grade scalability.
- Monitor containers with Prometheus and centralize logs with ELK.
- Follow best practices like running as non-root and automating CI/CD.
Call to Action: Start containerizing your Spring Boot application using the provided examples. Deploy locally with Docker Compose and scale to production with Kubernetes. Share your feedback or questions in the comments or on X to join the developer community!