Command Palette

Search for a command to run...

Hectal
← All projects

Project 2 of 12 · project brief

Dockerize the Spring Boot Service

A small, secure, cache-friendly image for the API, pushed to a registry with proper tags.

Beginner 1 day 4 milestones

The scenario

Project 1 works, but 'it works on that VM' doesn't scale. The team wants the API packaged as a container image that runs identically on a laptop, in CI, and in production. Your brief: write a production-grade Dockerfile, keep the image small and non-root, and publish versioned images to a registry.

Before you start

Stack

DockerMavenEclipse Temurin JRE 21Spring Boot layered JARsDocker Hub / GHCR / ECRTrivy

Target architecture

Project 2 architecturediagram
Rendering diagram…

Deliverables and requirements

You will hand in

  • A multi-stage Dockerfile and .dockerignore
  • An image under ~250 MB running as a non-root user
  • Images pushed with a semantic version tag and a Git SHA tag
  • A Trivy scan report with no critical vulnerabilities

Functional

  • docker run -p 8080:8080 -e SPRING_DATASOURCE_URL=... shoplite-api:<tag> starts the API
  • Configuration only via environment variables (12-factor)
  • A HEALTHCHECK or health endpoint usable by orchestrators

Non-functional

  • Rebuild after a code-only change takes seconds (dependency layer cached)
  • Non-root user, no build tools or source in the final image
  • JVM respects container memory limits (-XX:MaxRAMPercentage)

Milestones

  1. 1

    A naive image, measured

    Done when: A working single-stage image, and its size and build time as a baseline.

    • Write a Dockerfile FROM maven:3.9-eclipse-temurin-21 that builds and runs the JAR
    • Record image size (docker images) and rebuild time after changing one line of code

    Prove it works

    terminal
    $ docker images shoplite-api:naive --format '{{.Size}}'
    ── expected output ──
    812MB
  2. 2

    Multi-stage with layer caching

    Done when: A build stage with cached dependencies and a small JRE runtime stage.

    • Stage 1: copy pom.xml first, run mvn dependency:go-offline, then copy src and package
    • Stage 2: java -Djarmode=tools -jar app.jar extract --layers to split dependencies from app classes
    • Stage 3: eclipse-temurin:21-jre, copy layers in order of change frequency
    • Add .dockerignore (target/, .git, IDE files)
    Stuck? Hints
    • Order COPY instructions from least to most frequently changing so code edits don't invalidate the dependency layer.
    • docker build --progress=plain shows which steps are CACHED.

    Prove it works

    terminal
    $ docker build -t shoplite-api:dev . && docker images shoplite-api:dev --format '{{.Size}}'
    ── expected output ──
    241MB
  3. 3

    Harden it

    Done when: Non-root, minimal, container-aware runtime.

    • Create and switch to a non-root user (USER 10001)
    • Set JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=75
    • Add a HEALTHCHECK (or rely on orchestrator probes) and EXPOSE 8080
    • Scan with trivy image and fix critical findings (update base image)

    Prove it works

    terminal
    $ docker run --rm shoplite-api:dev id; trivy image --severity CRITICAL --exit-code 1 shoplite-api:dev && echo clean
    ── expected output ──
    uid=10001 gid=10001 groups=10001
    clean
  4. 4

    Tag and push

    Done when: Versioned, traceable images in a registry.

    • Tag with the app version and sha-<short git sha>; never rely on latest
    • Push to GHCR, Docker Hub, or ECR
    • Add OCI labels (org.opencontainers.image.revision, source)

    Prove it works

    terminal
    $ docker buildx imagetools inspect ghcr.io/<you>/shoplite-api:sha-3f2a1c9 | head -3
    ── expected output ──
    Name: ghcr.io/<you>/shoplite-api:sha-3f2a1c9
    MediaType: application/vnd.oci.image.index.v1+json
    Digest: sha256:9b1f...

Would you run this in production?

  • ☐Multi-stage build; no JDK, Maven, or source in the runtime image
  • ☐Non-root user; read-only root filesystem compatible
  • ☐Pinned base image versions (and ideally digests), rebuilt regularly for patches
  • ☐Immutable tags (version + SHA); latest never deployed
  • ☐Vulnerability scan in the build

Stretch goals

  • Build multi-arch (amd64 + arm64) with docker buildx
  • Try a distroless or Chiseled Ubuntu base and compare size
  • Build with Jib or Spring Boot Buildpacks and compare to your Dockerfile

Show it off

Résumé bullet

Containerised a Spring Boot service with a multi-stage, non-root Dockerfile, cutting image size from 812 MB to 241 MB and rebuild time to seconds via layer caching; images scanned and versioned by Git SHA.

Demo script

  • Change one line of code and rebuild: show the CACHED dependency layers
  • Run docker history to explain the layers
  • Show the Trivy report

Interview questions about this project

01

How do you make Docker builds for a Java app fast?

02

Why not run containers as root?