How Layered Docker Builds Shrink Spring Boot Image Size and Speed Up Deployments
This guide explains how to use Spring Boot 2.3's layered image feature and Dockerfile optimizations to reduce image size, avoid duplicate layers, and accelerate pushes by only updating changed layers, with detailed commands and analysis.
In containerized deployments of Spring Boot applications, the common practice of using a base Java image and adding a single JAR layer leads to large image sizes and increased bandwidth when pushing updates to the cloud. To address this, the article explores Spring Boot 2.3's new layered image build capability and demonstrates how to restructure Dockerfiles to minimize layer duplication.
Prerequisites
Spring Boot version >= 2.3
Oracle JDK 8u201
Docker 20.10.13
Traditional Single‑JAR Build
A typical Dockerfile copies the JAR and an entrypoint script into the image:
FROM registry.xxx.com/base/oracle-jdk:8u201
ENV TZ=Asia/Shanghai
ENV LC_ALL=en_US.utf8
WORKDIR /app
ADD ./xxx-1.0.0.jar /app/lib/xxx-1.0.0.jar
ADD ./entrypoint.sh /app/bin/entrypoint.sh
ENTRYPOINT exec bash /app/bin/entrypoint.sh
RUN ln -s /app/logs /app/log && chown 1001.1001 -R /appThe base image is 622 MB; adding the 65 MB JAR results in a 757 MB final image, larger than expected. Inspection of the image history shows three base layers (CentOS, yum install, JDK) and an extra layer created by the RUN ln -s … && chown command, which unintentionally duplicates the JAR layer because changing file ownership creates a new layer with the same size.
Optimized Dockerfile
To avoid the duplicate layer, the Dockerfile is rewritten to create directories, set permissions, and copy the JAR with the correct ownership in a single step:
FROM registry.xxx.com/base/oracle-jdk:8u201
ENV TZ=Asia/Shanghai
ENV LC_ALL=en_US.utf8
RUN mkdir -pv /app/{bin,lib,logs} && ln -s /app/logs /app/log && chown 1001:1001 -R /app
WORKDIR /app
COPY --chown=1001:1001 ./xxx-1.0.0.jar /app/lib/xxx-1.0.0.jar
COPY --chown=1001:1001 ./entrypoint.sh /app/bin/entrypoint.sh
ENTRYPOINT exec bash /app/bin/entrypoint.shAfter this change, the image contains only the JAR copy layer, reducing the final size to roughly 622 MB + 65 MB = 687 MB.
Layered Build with Spring Boot 2.3
Spring Boot 2.3 can extract the JAR into separate layers (dependencies, spring‑boot‑loader, snapshot‑dependencies, application). A multi‑stage Dockerfile is used:
# Builder stage
FROM registry.xxx.com/base/oracle-jdk:8u201 as builder
WORKDIR /app
COPY --chown=1001:1001 ./xxx-1.0.0.jar /app/lib/xxx-1.0.0.jar
RUN java -Djarmode=layertools -jar /app/lib/xxx-1.0.0.jar extract
# Runtime stage
FROM registry.xxx.com/base/oracle-jdk:8u201
ENV TZ=Asia/Shanghai
ENV LC_ALL=en_US.utf8
RUN mkdir -pv /app/logs && ln -s /app/logs /app/log && chown 1001:1001 -R /app
WORKDIR /app
COPY --chown=1001:1001 --from=builder /app/dependencies/ ./
COPY --chown=1001:1001 --from=builder /app/spring-boot-loader/ ./
COPY --chown=1001:1001 --from=builder /app/snapshot-dependencies/ ./
COPY --chown=1001:1001 --from=builder /app/application/ ./
ENTRYPOINT ["java","org.springframework.boot.loader.JarLauncher"]The builder extracts the JAR into four directories. The dependencies layer is the largest (≈65 MB), while the application code occupies only ~304 KB.
Verification
Running docker image history on the layered image shows separate COPY layers for each extracted directory, confirming that only the changed layer (e.g., application) needs to be pushed when the code updates.
# Build new version (1.0.1) – only application layer changes
docker build -t registry.xxx.com/layer/build:new-1.0.1 .
# Push – only a ~300 KB layer is transferred
docker push registry.xxx.com/layer/build:new-1.0.1Running the container with the appropriate user and JVM options demonstrates successful startup and prints heap statistics, proving that the layered image works as intended.
Conclusion
By leveraging Spring Boot's layered build mode and careful Dockerfile design, developers can dramatically reduce image size, eliminate redundant layers caused by ownership changes, and speed up CI/CD pipelines by pushing only the layers that actually changed. This approach is especially valuable in large‑scale cloud environments where bandwidth and storage costs are significant.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
