exit lab
Backend Engineering
Level 10·devops·guide·intermediate

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.

55 minutes Docker BuildKit, Java 21 runtime, Maven wrapper, GitHub Actions current secure-use guidance Updated 2026-07-16
DockerMavenGitHub ActionsSpring BootNginx

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

Developer push
Compile
Unit tests
Integration tests
Build image
Scan + attest
Deploy
Readiness
Smoke test
Monitor
Promote or rollback
Text alternative: A developer push triggers compile, unit and integration tests, image build and scanning. The immutable image is deployed, checked for readiness and smoke behavior, monitored, then promoted or rolled back.

Simple example

Dockerfile
# 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 --from=build --chown=app:app /workspace/target/*.jar app.jar
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Backend example

.github/workflows/backend-ci.yml
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. 1

    Pull request

    Compile, static analysis, unit tests, PostgreSQL integration tests, dependency review, and Dockerfile checks run without deployment credentials.

  2. 2

    Artifact

    Main builds and scans one image, records source revision and dependency metadata, then pushes it to a restricted registry.

  3. 3

    Migration

    Run a backward-compatible migration as an explicit controlled job; do not let every application replica race to migrate.

  4. 4

    Deploy

    Roll out the digest with environment configuration and secret references, bounded resources, readiness/liveness, and graceful shutdown.

  5. 5

    Verify

    Run smoke tests and watch error, latency, saturation, and business signals during a defined observation window.

  6. 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

ApproachBenefitCost
DockerfileExplicit portable image recipeYou own patch and layer decisions
BuildpackCurated layers and fast rebasesLess low-level control
RollingEfficient gradual replacementOld/new versions overlap
Blue-greenFast traffic switch and app rollbackDouble 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. 1

    Image

    Build the Task API with a multi-stage Dockerfile, non-root runtime, .dockerignore, and digest-aware base policy.

  2. 2

    Pipeline

    Add compile, unit, integration, image build, scan, artifact, and approval stages with least privilege.

  3. 3

    Deploy

    Run a local rolling simulation, verify readiness and smoke behavior, send SIGTERM, and confirm graceful completion.

  4. 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.