<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Libre+Franklin:wght@400..800&family=JetBrains+Mono:wght@400;500;700&display=swap" />
Late edition Stop the presses

Suspect at large

A backend developer is fleeing the scene. The record is being set.

Setting the type Inking the plates Running the presses

Skip to the front page
Kathmandu, NepalThe Backend EditionEst. 2021

The personal record of a backend developer

Wednesday 5 August 2026Vol. ISelected works & notesPrice: one coffee
← Back to the dispatchesDevOps · 11 min read

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.

By · Backend Developer — Spring Boot & DevOps25 December 2023 · 11 min read

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

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

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/health endpoint 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.

dockerfile
# 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:

yaml
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):

yaml
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: 5

Service YAML (for User Service):

yaml
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

MySQL Deployment YAML:

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-pvc

Persistent Volume Claim (PVC):

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Secrets (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:

yaml
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.0

This 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:

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

application.yml:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health, metrics, prometheus

Prometheus Monitoring

Scrape metrics from the /actuator/prometheus endpoint:

yaml
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:

xml
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.2</version>
</dependency>

logback-spring.xml:

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:

yaml
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:
      - elasticsearch

Best Practices

  • Minimize Image Size:
  • Use slim base images (e.g., openjdk:17-jdk-slim).
  • Leverage multi-stage builds to exclude build tools.
  • Example:
dockerfile
    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:
yaml
    livenessProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 10
  • Environment Configuration:
  • Use environment variables for configuration.
  • Example:
yaml
    env:
      - name: SPRING_DATASOURCE_URL
        value: "jdbc:mysql://user-db:3306/user_db"
  • Logging:
  • Configure structured JSON logging:
java
    @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:
yaml
    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!

Backend developer specializing in Java and Spring Boot. Building scalable, reliable systems that power modern applications. This broadsheet is hand-set in Caslon and Franklin.

The desk

Tech stack

  • Java & Spring Boot
  • MySQL & PostgreSQL
  • Docker & Microservices
  • JWT & OAuth2
Case closed

System up since 2021 · Building backend systems · Learning new technologies · Contributing to open source

© 2026 Utsab Dahal · All rights reserved · Printed in Kathmandu