Spring Docker Deployment
Introduction
Deploying Spring Boot applications can be challenging, especially when dealing with different environments and dependencies. Docker provides an elegant solution by packaging your application with all its dependencies in a standardized unit called a container. This approach ensures that your application runs consistently across different environments.
In this tutorial, we'll explore how to containerize and deploy Spring Boot applications using Docker. Whether you're new to Docker or looking to improve your deployment workflow, this guide will help you understand the fundamentals and best practices.
Prerequisites
Before we begin, make sure you have:
- Basic knowledge of Spring Boot
- JDK 17 or later installed
- Maven or Gradle installed
- Docker installed on your machine
- A simple Spring Boot application to deploy
Understanding Docker Basics
Docker is a platform that uses containerization technology to package applications and their dependencies together. Here's why Docker is beneficial for Spring Boot applications:
- Consistency: Your application runs the same way everywhere
- Isolation: Containerized applications don't interfere with each other
- Efficiency: Docker containers require fewer resources than virtual machines
- Portability: Deploy anywhere that runs Docker
Creating a Dockerfile for Spring Boot
A Dockerfile is a text document containing instructions to build a Docker image. Let's create a basic Dockerfile for a Spring Boot application:
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Let's break down what each line does:
FROM eclipse-temurin:17-jdk-alpine
- Uses the Eclipse Temurin JDK 17 image as the baseVOLUME /tmp
- Creates a temporary volume to store dataARG JAR_FILE=target/*.jar
- Defines a build argument that points to our JAR fileCOPY ${JAR_FILE} app.jar
- Copies our JAR into the imageENTRYPOINT ["java","-jar","/app.jar"]
- Specifies the command to run when the container starts
Building a Docker Image
Once you have your Dockerfile, the next step is to build an image. First, make sure you've built your Spring Boot application:
# Using Maven
mvn clean package
# Or using Gradle
./gradlew build
Then build your Docker image:
docker build -t myapp:latest .
This command creates a Docker image named myapp
with the tag latest
. The .
tells Docker to look for the Dockerfile in the current directory.
Running Your Spring Boot Application in Docker
After building the image, you can run it as a container:
docker run -p 8080:8080 myapp:latest
This command:
- Creates and starts a container from the
myapp:latest
image - Maps port 8080 inside the container to port 8080 on your host machine
Your Spring Boot application should now be accessible at http://localhost:8080.
Optimizing Your Docker Image
The basic Dockerfile works, but we can improve it. Here's an optimized multi-stage build Dockerfile:
# Build stage
FROM maven:3.8.6-eclipse-temurin-17-alpine AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# Run stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
Benefits of this approach:
- Smaller final image (JRE instead of JDK)
- Build happens inside Docker (no need for local Maven)
- Dependencies are downloaded only when the pom.xml changes
Configuring Spring Boot for Docker
Spring Boot applications often need different configurations for different environments. You can use environment variables in Docker to override Spring properties:
docker run -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e SPRING_DATASOURCE_URL=jdbc:mysql://db-host:3306/mydb \
-e SPRING_DATASOURCE_USERNAME=user \
-e SPRING_DATASOURCE_PASSWORD=password \
myapp:latest
In your application.properties or application.yml, use these variables:
spring:
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/mydb}
username: ${SPRING_DATASOURCE_USERNAME:root}
password: ${SPRING_DATASOURCE_PASSWORD:password}
Docker Compose for Multi-Container Applications
Most Spring Boot applications require other services like databases or caches. Docker Compose helps manage multi-container applications. Here's a sample docker-compose.yml
:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/mydb
- SPRING_DATASOURCE_USERNAME=user
- SPRING_DATASOURCE_PASSWORD=password
depends_on:
- db
db:
image: mysql:8.0
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_ROOT_PASSWORD=rootpassword
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
Start all services with:
docker-compose up
This will start both your Spring Boot application and a MySQL database, with the correct networking between them.
Practical Example: Containerizing a Spring Boot REST API
Let's walk through a concrete example of containerizing a simple Spring Boot REST API that uses a database.
1. Sample Spring Boot Application
Here's a simple REST API for a todo application:
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoRepository todoRepository;
public TodoController(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@GetMapping
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
@PostMapping
public Todo createTodo(@RequestBody Todo todo) {
return todoRepository.save(todo);
}
// Other CRUD operations
}
2. Define Application Properties
# application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:h2:mem:tododb}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:sa}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:}
spring.jpa.hibernate.ddl-auto=update
3. Build and Package the Application
mvn clean package
4. Create a Dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
5. Create a Docker Compose File
version: '3.8'
services:
api:
build: .
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/todos
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=secret
depends_on:
- db
db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=todos
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=secret
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres-data:
6. Run the Application
docker-compose up
After executing this command, you should be able to access your API at http://localhost:8080/api/todos.
Health Checks and Monitoring
Docker provides health checks to monitor the health of your containers. For a Spring Boot application, you can leverage the Actuator's health endpoint:
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q -O /dev/null http://localhost:8080/actuator/health || exit 1
Add the Spring Boot Actuator dependency to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
And enable the health endpoint in your application.properties:
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always
Best Practices for Spring Docker Deployment
- Use official base images: Stick with official Java/JDK images for security and updates
- Minimize layers: Combine RUN commands to reduce the number of layers
- Use .dockerignore: Create a .dockerignore file to exclude unnecessary files
- Don't run as root: Use a non-root user for security
- Externalize configuration: Use environment variables or config files
- Handle graceful shutdown: Configure proper shutdown hooks in your Spring application
- Label your images: Add metadata to your images using LABEL instructions
Example of applying these practices:
FROM eclipse-temurin:17-jre-alpine as builder
WORKDIR /app
COPY target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
HEALTHCHECK --interval=30s --timeout=3s CMD wget -q -O /dev/null http://localhost:8080/actuator/health || exit 1
LABEL maintainer="[email protected]" \
version="1.0" \
description="Spring Boot Todo API"
Deploying to Production
When deploying to production, consider:
- Container orchestration: Use Kubernetes or Docker Swarm for managing containers at scale
- Secrets management: Never hardcode sensitive information; use Docker Secrets or environment variables
- Persistent storage: Configure volumes correctly for data persistence
- Logging: Configure log aggregation for containerized applications
- CI/CD integration: Automate your Docker build and deployment process
Common Issues and Troubleshooting
Container Exits Immediately
If your container exits immediately after starting, check:
- Application errors in the logs:
docker logs <container-id>
- Port conflicts: Ensure the required ports are available
- Memory issues: Check if the container has enough memory
Out of Memory Errors
Add JVM memory settings to your Docker run command:
docker run -p 8080:8080 -e JAVA_OPTS='-Xmx512m -Xms256m' myapp:latest
Database Connection Issues
When your application can't connect to the database:
- Check the database credentials
- Verify that the database container is running
- Ensure the networking is correctly set up (especially in Docker Compose)
Summary
In this tutorial, we covered:
- Creating a Dockerfile for Spring Boot applications
- Building and running Docker images
- Optimizing Docker images using multi-stage builds
- Configuring Spring Boot applications for containerization
- Using Docker Compose for multi-container applications
- Best practices for production deployment
- Troubleshooting common issues
Docker provides a powerful way to package and deploy Spring Boot applications consistently across various environments. By containerizing your application, you gain portability, consistency, and easier scaling.
Additional Resources
- Spring Boot Docker Documentation
- Docker Documentation
- Spring Boot Actuator Documentation
- Docker Compose Documentation
Exercises
- Basic: Create a Docker image for a simple Spring Boot "Hello World" application
- Intermediate: Set up a Spring Boot application with PostgreSQL using Docker Compose
- Advanced: Create a CI/CD pipeline that builds a Docker image and deploys it to a registry
- Expert: Implement Docker health checks and container orchestration for a Spring microservices architecture
Happy containerizing your Spring Boot applications!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)