Docker and CI/CD for Spring Boot
Package a reproducible Spring Boot artifact in a multi-stage non-root image and promote it through secure CI, verification, deployment, and rollback gates.
Learning objectives
- Build a minimal non-root Spring Boot container with reproducible inputs
- Design CI/CD gates that verify the artifact before and after deployment
What it is
Containerization packages a Spring Boot artifact and runtime filesystem into an image. CI validates source and creates an immutable artifact; delivery deploys that same artifact through controlled environments; deployment completes only after health and smoke verification.
Why it matters
A successful local JAR is not production evidence. Reproducibility, base-image provenance, least privilege, secrets, migrations, supply-chain controls, probes, resource limits, and rollback determine whether an artifact can be operated safely.
How it works
Delivery flow
Simple example
# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk-jammy AS build
WORKDIR /workspace
COPY .mvn/ .mvn/
COPY mvnw pom.xml ./
RUN ./mvnw -B -q -DskipTests dependency:go-offline
COPY src/ src/
RUN ./mvnw -B -DskipTests package
FROM eclipse-temurin:21-jre-jammy AS runtime
RUN groupadd --system --gid 10001 app && useradd --system --uid 10001 --gid app --home /app app
WORKDIR /app
COPY /workspace/target/*.jar app.jar
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]Backend example
name: backend-ci
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
# In the repository, replace every <verified-full-sha> with a full
# commit SHA from the official action repository.
- uses: actions/checkout@<verified-full-sha>
- uses: actions/setup-java@<verified-full-sha>
with:
distribution: temurin
java-version: "21"
cache: maven
- run: ./mvnw -B verify
- run: docker build --pull -t task-api:${{ github.sha }} .The placeholder is intentional: GitHub recommends full-length commit SHAs as the immutable way to consume actions. Resolve and review the current SHA when implementing the repository workflow rather than copying a stale value from learning content.
Production example
- 1
Pull request
Compile, static analysis, unit tests, PostgreSQL integration tests, dependency review, and Dockerfile checks run without deployment credentials.
- 2
Artifact
Main builds and scans one image, records source revision and dependency metadata, then pushes it to a restricted registry.
- 3
Migration
Run a backward-compatible migration as an explicit controlled job; do not let every application replica race to migrate.
- 4
Deploy
Roll out the digest with environment configuration and secret references, bounded resources, readiness/liveness, and graceful shutdown.
- 5
Verify
Run smoke tests and watch error, latency, saturation, and business signals during a defined observation window.
- 6
Rollback
Stop promotion or restore the previous application digest. Database rollback relies on expand/contract compatibility, not wishful down scripts.
Common mistakes
- Running the application as root
- Copying source, Maven cache, or secrets into the runtime image
- Using latest tags for production identity
- Giving the workflow broad write permissions
- Pinning actions only to mutable tags
- Running destructive migrations during every instance startup
- Treating a passing health endpoint as a complete smoke test
Best practices
- Use multi-stage builds and .dockerignore
- Choose trusted minimal bases and patch them
- Run as an explicit non-root UID/GID
- Give jobs least-privilege permissions and environments separate approval
- Keep secrets out of images and logs
- Use readiness for traffic and liveness only for unrecoverable process state
- Practice rollback and restore
Trade-offs
| Approach | Benefit | Cost |
|---|---|---|
| Dockerfile | Explicit portable image recipe | You own patch and layer decisions |
| Buildpack | Curated layers and fast rebases | Less low-level control |
| Rolling | Efficient gradual replacement | Old/new versions overlap |
| Blue-green | Fast traffic switch and app rollback | Double capacity and data compatibility |
Interview questions
- Why use a multi-stage build?
- Why run as non-root?
- What should readiness and liveness mean?
- How do you deploy a database migration safely?
- Why pin GitHub Actions to a full SHA?
- What makes rollback possible?
Hands-on task
- 1
Image
Build the Task API with a multi-stage Dockerfile, non-root runtime, .dockerignore, and digest-aware base policy.
- 2
Pipeline
Add compile, unit, integration, image build, scan, artifact, and approval stages with least privilege.
- 3
Deploy
Run a local rolling simulation, verify readiness and smoke behavior, send SIGTERM, and confirm graceful completion.
- 4
Recover
Roll back the application image while keeping an expand/contract schema compatible.
References
- Docker: Docker Build Best Practices — https://docs.docker.com/build/building/best-practices/
- GitHub: Secure Use Reference for GitHub Actions — https://docs.github.com/en/actions/reference/security/secure-use
- Spring: Spring Boot Graceful Shutdown — https://docs.spring.io/spring-boot/reference/web/graceful-shutdown.html
- NGINX: NGINX Reverse Proxy Guide — https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
Retrieval check
Before you continue
- Explain the main idea in your own words without rereading the page.
- Name one production problem this knowledge helps prevent.
- Describe where you would apply it in a Spring Boot project.
Source metadata
Primary references checked on 2026-07-16. Version scope is stated at the top of this page.