task(docker): Optimize java-base image and runtime Dockerfile to eliminate duplicate package installation
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
The java-base Docker image and the runtime dotCMS Dockerfile currently duplicate significant work in every build. The runtime Dockerfile's final stage starts from bare ubuntu:24.04 and re-installs nearly every package already present in java-base, including running the full pgdg PostgreSQL client setup twice across the combined pipeline.
Two packages (libmimalloc2.0, libarchive-tools) exist only in the final stage and are not in java-base — that gap is what forces the fresh base approach today. Adding them to java-base (Phase 1) unblocks using java-base as the Stage 2 foundation (Phase 2).
Current Pipeline Structure
docker/java-base/Dockerfile
└─ Stage 1 (base-builder): ubuntu:24.04
• apt install: zip, unzip, wget, curl, gnupg, libtcnative-1, tzdata, tini,
ca-certificates, openssl, libapr1, libpq-dev
• Download + install SDKMAN
• SDKMAN installs specified Java version
• jlink builds custom minimal JRE → /java
• pgdg repo setup + postgresql-client-18 installed
• Cleanup: purge zip, unzip, wget, curl, libpq-dev; rm SDKMAN
└─ Stage 2 (FROM scratch): flattens everything to 1 layer
└─ Published as: dotcms/java-base:{SDKMAN_JAVA_VERSION}
dotCMS/src/main/docker/original/Dockerfile
└─ Stage 1 (container-base): FROM dotcms/java-base:{version}
• Creates dotcms user/group + /srv, /data directory structure
• COPY maven build artifacts → /srv
• Symlinks tomcat, chmod scripts
└─ Stage 2 (final): FROM ubuntu:24.04 ← PROBLEM: discards java-base, reinstalls everything
• apt install (block 1): wget, curl, gnupg, tini, zip, unzip, libtcnative-1,
tzdata, ca-certificates, libmimalloc2.0, openssl,
libapr1, libarchive-tools, libpq-dev
• apt install (block 2): pgdg repo setup + postgresql-client-18
• Re-creates dotcms user/group
• COPY /java, /srv, /data from Stage 1
Identified Inefficiencies
1. Stage 2 discards java-base and re-installs everything from scratch
Package overlap between java-base (retained after cleanup) and Stage 2 final:
| Package | In java-base | In Stage 2 final |
|---|---|---|
| tini, libtcnative-1, tzdata, ca-certificates | ✅ | ✅ reinstalled |
| openssl, libapr1 | ✅ | ✅ reinstalled |
| wget, curl, gnupg | ✅ purged as build tools | ✅ reinstalled |
| postgresql-client-18 + pgdg setup | ✅ full setup | ✅ full setup AGAIN |
| libmimalloc2.0, libarchive-tools | ❌ missing | ✅ Stage 2 only |
The two missing packages are the only thing preventing Stage 2 from using java-base directly.
2. pgdg PostgreSQL client setup runs twice per pipeline
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh — a network call to configure the PostgreSQL apt repository — runs once when building java-base and again in Stage 2 of the runtime Dockerfile. This is the slowest single step in Stage 2.
3. Stage 2 has two separate apt RUN blocks with two apt update calls
RUN apt update && apt install -y ...basic packages... && rm -rf /var/lib/apt/lists/*
RUN apt update && apt install -y postgresql-common && ...pgdg setup...
No technical reason for the split — two apt update calls, two layers.
4. COPY /java from Stage 1 is redundant
Once Stage 2 uses java-base as its base, /java is already present. Copying it again from Stage 1 is redundant.
5. User/group creation is duplicated across stages
Unavoidable today because Stage 2 starts fresh — goes away once Stage 2 uses java-base.
6. FROM scratch flattens layers but does not reduce image size
COPY --from=base-builder / / copies the entire filesystem. Benefit is reduced layer count for push/pull, not reduced image size.
7. find-based chmod is a full-tree traversal
find /srv/ -type f -name "*.sh" -exec chmod a+x {} \;
Walks every file under /srv after the full Maven artifact copy. Should use COPY --chmod=755 or chmod on known paths instead.
Changes Being Made
Phase 1 — docker/java-base/Dockerfile (backwards compatible, PR 1)
| Change | Detail |
|---|---|
| Add packages | libmimalloc2.0 and libarchive-tools added to apt install (neither is purged, so both survive into the final image) |
| Add ENV | ENV JAVA_HOME="/java" added alongside existing env vars |
Purely additive — no existing consumers break.
Gate: Updated base image must be built, published to the registry, and verified before PR 2 can land.
Phase 2 — dotCMS/src/main/docker/original/Dockerfile (PR 2, after base is live)
| Change | Detail |
|---|---|
| Stage 2 FROM | ubuntu:24.04 → dotcms/java-base:${SDKMAN_JAVA_VERSION} |
| Stage 1 FROM | Same update for consistency |
| Remove | Basic packages RUN apt update && apt install block (~17 lines) |
| Remove | PostgreSQL pgdg + RUN apt update && apt install postgresql-common block (~9 lines) |
| Remove | COPY --from=container-base /java /java — already in java-base |
| Keep | User/group creation (java-base does not create the dotcms user) |
| Keep | COPY --from=container-base /srv /srv and /data /data |
Net result: ~27 lines removed from the final stage. pgdg runs once per pipeline instead of twice.
Note: Immutable image digest pinning for the base image reference is tracked separately — see the linked issue.
Best Practices Applied
- Single responsibility for base images —
java-baseowns all OS-level runtime dependencies; the runtime Dockerfile owns only the app layer - Additive-only base image changes — Phase 1 adds packages without removing anything, preserving backwards compatibility
- Eliminate duplicate work in multi-stage builds — each package installed exactly once in the pipeline
- One RUN block per logical operation — reduces layer count, eliminates duplicate
apt updatecalls - Use COPY --chmod instead of find/chmod — known script paths at build time; avoids full-tree traversal post-copy
Files
docker/java-base/DockerfiledotCMS/src/main/docker/original/Dockerfile
Implementation plan: docs/plans/2026-02-25-docker-base-image-optimization.md
Acceptance Criteria
Phase 1 (PR 1):
-
libmimalloc2.0andlibarchive-toolsare present in the publisheddotcms/java-baseimage -
JAVA_HOME=/javais exported fromjava-base - Existing consumers of
dotcms/java-base:{java_version}continue to work without changes
Phase 2 (PR 2):
- Runtime Dockerfile Stage 1 and Stage 2 both reference
dotcms/java-base:${SDKMAN_JAVA_VERSION} - Both
apt installblocks are removed from Stage 2 -
COPY --from=container-base /java /javais removed from Stage 2 - pgdg PostgreSQL client setup runs exactly once per pipeline
-
docker run --rm dotcms/dotcms:local-test java -versionsucceeds -
docker run --rm dotcms/dotcms:local-test /usr/bin/pg_dump --versionsucceeds
Priority
Medium
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read docs/plans/2026-02-25-docker-base-image-optimization.md, then inspect docker/java-base/Dockerfile and dotCMS/src/main/docker/original/Dockerfile. Build and verify the updated java-base before changing the runtime stages. Done means the listed packages and JAVA_HOME are present, duplicate installation and /java copying are removed, and the java and pg_dump runtime checks pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, java, postgresql, ubuntu
- Domain
- build-system, devops, infrastructure
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100