dotCMS / dotCMS/core

task(docker): Optimize java-base image and runtime Dockerfile to eliminate duplicate package installation

Open
#34,781 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : Containerization stale
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.04dotcms/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 imagesjava-base owns 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 update calls
  • Use COPY --chmod instead of find/chmod — known script paths at build time; avoids full-tree traversal post-copy

Files

  • docker/java-base/Dockerfile
  • dotCMS/src/main/docker/original/Dockerfile

Implementation plan: docs/plans/2026-02-25-docker-base-image-optimization.md


Acceptance Criteria

Phase 1 (PR 1):

  • libmimalloc2.0 and libarchive-tools are present in the published dotcms/java-base image
  • JAVA_HOME=/java is exported from java-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 install blocks are removed from Stage 2
  • COPY --from=container-base /java /java is removed from Stage 2
  • pgdg PostgreSQL client setup runs exactly once per pipeline
  • docker run --rm dotcms/dotcms:local-test java -version succeeds
  • docker run --rm dotcms/dotcms:local-test /usr/bin/pg_dump --version succeeds

Priority

Medium

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.