Containerizing a Spring Boot App

Package your app into a portable image with a Dockerfile , speed up rebuilds with layered jars , skip the Dockerfile entirely using buildpacks , and configure everything through environment variables .

Learn Containerizing a Spring Boot App in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You should be able to build a runnable Spring Boot jar and understand its profiles and external configuration . Basic familiarity with the command line and Docker concepts helps.

💡 Analogy: A Docker image is a standardized shipping container : your app, its JRE, and everything it needs are sealed inside, so it runs the same on your laptop, a colleague's machine, or a cloud server. The Dockerfile is the packing list. Layered jars are like packing the heavy, unchanging machinery at the bottom and your daily paperwork on top — when only the paperwork changes, you don't repack the whole container. Buildpacks are a professional packing service that loads the container correctly for you. And the environment variables are the shipping label that tells the same container which port to dock at.

Build the container once; the label (env vars) decides where and how it runs.

Containers follow the twelve-factor rule: one immutable image, configured at runtime via environment variables . Spring Boot reads them automatically; the demo mimics that lookup-with-default.

Docker caches image layers by content. Put rarely-changing dependencies in their own layers and your frequently-changing app code last, so a routine code change only rebuilds the small top layer. The demo models that cache decision.

Build with the JDK, run on a slim JRE. Extract the Spring Boot jar into layers and copy the least-changing ones first to maximize cache hits.

Spring Boot's spring-boot:build-image (Maven) and bootBuildImage (Gradle) use Cloud Native Buildpacks to produce an optimized, layered, non-root image — no Dockerfile to maintain.

Answer: the application layer (your own code) — it changes most often, so it's the top, most-frequently-rebuilt layer.

Answer: use buildpacks via mvn spring-boot:build-image or ./gradlew bootBuildImage .

Answer: from environment variables at runtime (e.g. SPRING_DATASOURCE_URL binds to spring.datasource.url ).

🎯 YOUR TURN — Env Config With Defaults

Implement env(key, default) and print the resolved profile and port.

🧩 MINI-CHALLENGE — Simulate the Layer Cache

Given the first changed layer index, mark every layer from there onward as REBUILD and count them.

You can now write a multi-stage, layered Dockerfile, explain why layer order maximizes the build cache, build images without a Dockerfile using buildpacks, and configure the same image across environments with environment variables.

Next up: the Capstone — assembling a complete REST API from everything you've learned.

Practice quiz

What does a Dockerfile describe?

  • A database schema
  • The steps to build a container image
  • A REST endpoint
  • A unit test

Answer: The steps to build a container image. A Dockerfile is a recipe of instructions (FROM, COPY, ENTRYPOINT, etc.) that build a reproducible container image.

Which Dockerfile instruction sets the base image?

  • RUN
  • FROM
  • CMD
  • LABEL

Answer: FROM. FROM declares the base image the new image is built on, such as a JRE image for running a Spring Boot jar.

Which instruction defines the command that runs when the container starts?

  • ENTRYPOINT
  • WORKDIR
  • ENV
  • EXPOSE

Answer: ENTRYPOINT. ENTRYPOINT (or CMD) specifies the process the container runs, e.g. java -jar app.jar.

Why split a Spring Boot jar into layers in the image?

  • To encrypt the jar
  • To make the jar smaller than the source
  • So unchanged layers (dependencies) are cached and only changed layers rebuild/ship
  • Because Docker requires exactly four layers

Answer: So unchanged layers (dependencies) are cached and only changed layers rebuild/ship. Layered jars put rarely-changing dependencies in their own layers, so Docker's layer cache avoids re-shipping them on each rebuild.

In a layered Spring Boot jar, which layer changes most often?

  • dependencies
  • spring-boot-loader
  • snapshot-dependencies
  • application (your own classes/resources)

Answer: application (your own classes/resources). Your application code changes most frequently, so it belongs in the last, most-often-rebuilt layer.

What does Spring Boot's buildpacks support (mvn spring-boot:build-image) do?

  • Runs your tests only
  • Builds an optimized OCI image WITHOUT you writing a Dockerfile
  • Deploys to production automatically
  • Generates JavaDoc

Answer: Builds an optimized OCI image WITHOUT you writing a Dockerfile. spring-boot:build-image uses Cloud Native Buildpacks to produce a sensible, layered image with no hand-written Dockerfile.

A benefit of buildpacks over a hand-written Dockerfile is...

  • They never produce images
  • They require root by default
  • They only work offline
  • They apply best practices (layering, JRE choice, non-root) automatically

Answer: They apply best practices (layering, JRE choice, non-root) automatically. Buildpacks encode best practices — appropriate base image, layering, and a non-root user — so you don't maintain a Dockerfile.

How should configuration like the database URL be supplied to a container?

  • Hard-coded in the Dockerfile
  • Via environment variables at runtime
  • Recompiled into the jar each time
  • In a comment

Answer: Via environment variables at runtime. Twelve-factor config: pass settings as environment variables so the same image runs in any environment without rebuilding.

Spring Boot maps an env var SPRING_DATASOURCE_URL to which property?

  • server.port
  • logging.level
  • spring.datasource.url
  • info.app.name

Answer: spring.datasource.url. Relaxed binding maps SPRING_DATASOURCE_URL to spring.datasource.url, so env vars override properties.

Why prefer a JRE base image over a full JDK for the final runtime image?

  • The JRE can't run jars
  • A smaller runtime image (no compiler/tools) is leaner and has less attack surface
  • The JDK is required at runtime
  • It makes builds slower

Answer: A smaller runtime image (no compiler/tools) is leaner and has less attack surface. You compile with the JDK but only need a JRE to run; a slimmer runtime image is smaller and more secure.