Skip to content

LDEV-5882 non-root user and read-only rootfs support for Lucee 6.2+#107

Open
justincarter wants to merge 1 commit into
masterfrom
feature/nonroot-user-and-readonly-rootfs
Open

LDEV-5882 non-root user and read-only rootfs support for Lucee 6.2+#107
justincarter wants to merge 1 commit into
masterfrom
feature/nonroot-user-and-readonly-rootfs

Conversation

@justincarter

Copy link
Copy Markdown
Member

Add non-root user and read-only root filesystem support (Lucee 6.2+)

This PR adds opt-in support for running Lucee Docker images as a non-root user and with a read-only root filesystem. Both features target Lucee 6.2+ / Tomcat 11+ images (and 7.x). Earlier Lucee minors (5.x, 6.0, 6.1) are unchanged and continue to run as root with a writable filesystem.

Motivation

Modern container deployment environments (Kubernetes pod security standards, AWS ECS task hardening, internal security policies) increasingly require — or strongly prefer — containers that run as non-root and with a read-only root filesystem. These changes give Lucee users the ability to meet those requirements without forking the base image, while keeping default container behavior unchanged for users who don't opt in.

What's changed

Image-level capabilities (all opt-in; defaults unchanged):

  • A lucee system user is created (uid 999) on Lucee 6.2+ / Tomcat 11+ images. Opt in via USER lucee in a downstream Dockerfile, --user lucee on the CLI, user: "lucee" in docker-compose, securityContext.runAsUser: 999 in Kubernetes, or "user": "lucee" in an ECS task definition.
  • Writable runtime paths (/usr/local/tomcat/{logs,temp,work}, /opt/lucee/server-runtime, /tmp, plus nginx-specific paths) are declared as anonymous volumes so docker run --read-only works out of the box. Kubernetes and ECS Fargate still require explicit emptyDir / volume entries — this is documented in the README.
  • A new docker-entrypoint.sh handles the LUCEE_RUNTIME_DIR env var: when set, it seeds a writable copy of the Lucee server context from the read-only image at container start, then re-points Lucee to use the runtime directory. Without this seed step, Lucee can't write to its server context under a read-only filesystem.
  • prewarm.sh chowns the writable Tomcat and Lucee directories to the lucee user after warmup, so files produced by root during prewarm don't leave the lucee user unable to overwrite them at runtime (this avoids catalina.YYYY-MM-DD.log permission errors).

Configuration changes:

  • config/tomcat/11.0/web.xml: the lucee-server-directory and lucee-web-directory init-params in the CFMLServlet definition are commented out. Lucee now resolves these from the LUCEE_SERVER_DIR and LUCEE_WEB_DIR env vars (set in the Dockerfile). Tomcat 9.0 and 10.1 web.xml files are NOT modified, so 5.x / 6.0 / 6.1 continue to use the hardcoded init-param values.

Gating:

The new RUN block in Dockerfile is gated on Tomcat major version ≥ 11, matching the existing pattern at line 49. Since Lucee 6.2 / 7.0 / 7.1 are the only minors on Tomcat 11.0 in the build matrix, this scopes the feature to exactly the in-scope versions without requiring per-Lucee-minor logic.

Testing

  • All four image variants (6.2.6.19 tomcat, 6.2.6.19 nginx, 7.0.3.43 tomcat, 7.0.3.43 nginx) verified locally with three scenarios each: baseline (regression check), non-root user, and full read-only rootfs + LUCEE_RUNTIME_DIR. 5/5 checks pass on every variant.
  • A reusable smoke test script lives at tests/test-readonly-filesystem.sh and can be run manually against any locally-built or published Lucee image.
  • A manual-only GitHub Actions workflow (.github/workflows/test-readonly-filesystem.yml) is included for ad-hoc verification of published Docker Hub images. It does NOT run automatically on push or pull request — main build times are unaffected.

Files changed

File Change
Dockerfile New ENV LUCEE_SERVER_DIR / LUCEE_WEB_DIR; conditional RUN for Tomcat 11+ (creates pinned uid 999 lucee user, chowns runtime dirs, creates server-runtime dir, locks down lucee.jar perms); VOLUME declarations; ENTRYPOINT + restored CMD
Dockerfile.nginx Two RUN blocks gated on id lucee for nginx chowns + libcap2-bin / setcap; nginx-specific VOLUME declarations
config/tomcat/11.0/web.xml lucee-server-directory and lucee-web-directory init-params commented out
supporting/prewarm.sh Tail chown extended to include /usr/local/tomcat/{logs,temp,work} and /var/www
supporting/docker-entrypoint.sh New file — seeds LUCEE_RUNTIME_DIR from LUCEE_SERVER_DIR when set
tests/test-readonly-filesystem.sh New file — smoke test covering baseline / non-root / read-only scenarios
.github/workflows/test-readonly-filesystem.yml Rewritten — pulls a published image from Docker Hub and runs the smoke test (manual workflow_dispatch only)
README.md Updated version listings (7.0.3.43 latest, 6.2.6.19 LTS); new "Non-Root User and Read-Only Root Filesystem (Lucee 6.2+)" section with opt-in instructions per orchestrator; LUCEE_RUNTIME_DIR documented

Notable behavior changes for existing 6.2+ users

  • docker run without --rm will now create anonymous volumes for the declared paths. These accumulate on disk over time on dev / CI machines if not pruned. The README documents this and the available cleanup options (--rm, docker volume prune, named volumes in compose with docker compose down -v). Production deployments using explicit volumes are unaffected.
  • The lucee user (uid 999) is now created in the image but is not used unless explicitly opted in via --user lucee / USER lucee / etc. Default container behavior is still root.
  • The ENTRYPOINT is now docker-entrypoint.sh. It is a no-op when LUCEE_RUNTIME_DIR is unset, and forwards to the inherited CMD (catalina.sh run or supervisord …) via exec "$@". Behavior for default users is identical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant