perf(docker): move the runtime images to ubi9-micro and drop gosu - #33
Conversation
Ports floci-aws #3037, #3085 and #3108, completing ecosystem decision 0018. The floci-gcp pilot merged 2026-09-07 and its nightly went 87.9 MB to 53.4 MB compressed, a 39% cut, with all eight compat legs green; that was the gate for this repo. The chain, and it is a chain: ubi9-micro ships no useradd and no curl, so the gosu removal and the bash health probe are what make the smaller base viable. - entrypoint.sh: the privilege drop moves from gosu to coreutils `chroot --userspec=1001:0 --groups=... --skip-chdir /`. Supplementary groups pass by number, so the Docker socket's gid needs no /etc/group entry, and --skip-chdir keeps /app as the working directory where ./data resolves. - Dockerfile.native: the ubi9:9.7 tools stage that fetched gosu and installed shadow-utils is gone; runtime is ubi9-micro:9.7 and the user is one /etc/passwd line. - Dockerfile.native-package: same base and user setup, replacing ubi9-minimal plus an inline gosu install. This is the image release.yml publishes, and it also drops a curl-based healthcheck the new base could not run. - Dockerfile: drops its gosu download. Unlike floci-aws's Debian base, this one is Alpine, where busybox chroot has no --userspec; it works because the image ships GNU coreutils at /usr/sbin/chroot, ahead of busybox on PATH. Verified. Also drops `pip install --upgrade pip` from the compat image. It left a second, shadowing copy of pip in /usr/local while the RPM's stayed in /usr/lib; measured at 3.7 MB uncompressed on the equivalent floci-gcp image, with pip and the SDKs still working from the RPM copy. floci-aws never did this upgrade. - Dockerfile.compat: ubi9-micro has no package manager, so this can no longer extend the native image. It gets a ubi9-minimal tools stage and copies /app out, plus the portable Python trims from #3108. - healthcheck.sh: new, shared by the Dockerfiles. The old probe used curl, which ubi9-micro does not have, or wrote to /dev/tcp without reading the response; this one requires 200. - pom.xml: exclude the brotli4j platform JNI jars, which are never used. Verified on a built runtime image: uid=1001(floci) gid=0(root) through the entrypoint; a socket owned by gid 999 yields groups=0(root),999 end to end; /app/data writable; healthcheck exits 0 on a 200 and 1 on a 500. The USER directive stays absent on the compat image, as before: the image runs as root so entrypoint.sh can fix /app/data ownership before dropping privileges.
|
🎉 Thanks for your first pull request to Floci! Your CI checks need a maintainer to approve them before they run. That is GitHub's standard gate on first-time contributors, not a problem with your PR — so if the checks look like they are doing nothing, that is why. Once a maintainer approves, CI and the compatibility suite start automatically. Nothing is needed from you in the meantime. While you wait, a couple of things that make review faster:
Come join us in Slack — it is the fastest way to reach maintainers if you get stuck, or want feedback on an approach before investing more time in it. |
|
| Filename | Overview |
|---|---|
| docker/Dockerfile.compat | Rebuilds the compatibility image on UBI minimal, but calls the unavailable useradd command before installing shadow-utils. |
| docker/entrypoint.sh | Replaces gosu with a numeric UID/GID privilege drop using GNU coreutils chroot. |
| docker/healthcheck.sh | Adds a curl-free TCP health probe that requires an HTTP 200 response. |
| docker/Dockerfile.native-package | Moves the published native runtime to UBI 9 Micro and creates its user through /etc/passwd. |
| docker/Dockerfile.native | Aligns locally built native images with the smaller UBI 9 Micro runtime. |
| docker/Dockerfile | Removes gosu from the Alpine JVM image and relies on its GNU coreutils chroot. |
| pom.xml | Excludes unused platform-specific Brotli JNI artifacts. |
| src/main/resources/application.yml | Removes obsolete native-image arguments while retaining required protocol and initialization settings. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Native build artifacts] --> B[Dockerfile.native-package]
B --> C[UBI 9 Micro native image]
C -->|copy /app and entrypoint| D[Dockerfile.compat]
E[UBI 9 Minimal tools stage] -->|Python SDK and OCI CLI| D
D --> F[Compatibility image]
G[entrypoint.sh] -->|coreutils chroot| C
G -->|coreutils chroot| F
H[healthcheck.sh] --> C
H --> F
Reviews (1): Last reviewed commit: "perf(docker): move the runtime images to..." | Re-trigger Greptile
Summary
Completes ecosystem decision 0018 for this repo, porting floci-aws's image-size chain: #3037, #3085, #3108.
The pilot already proved it. floci-gcp #185 merged 2026-09-07 and its nightly went 87.9 MB → 53.4 MB compressed, a 39% cut, with all eight compat legs green. That was the gate 0018 set for this repo.
The three upstream commits are one change, not a menu:
ubi9-microships nouseraddand nocurl, so the gosu removal and the bash health probe are what make the smaller base viable.docker/entrypoint.shchroot --userspec=1001:0 --groups=... --skip-chdir /docker/Dockerfile.nativeubi9:9.7tools stage; runtime isubi9-micro:9.7, user is one/etc/passwdlinedocker/Dockerfile.native-packageubi9-minimalplus an inline gosu install. This is the imagerelease.ymlpublishesdocker/Dockerfiledocker/Dockerfile.compatubi9-microhas no package manager, so this can no longer extend the native image:ubi9-minimaltools stage, copies/appout, plus the portable Python trims from #3108docker/healthcheck.shcurl, whichubi9-microdoes not havepom.xml,application.ymlOne extra trim, specific to this repo
The compat image ran
python3.11 -m pip install --upgrade pip, which leaves a second, shadowing copy of pip in/usr/localwhile the RPM's stays in/usr/lib. floci-aws never did this. Measured on the equivalent floci-gcp image: 3.7 MB uncompressed, withpipand the SDKs still working from the RPM copy. Small, but free, and it converges on floci-aws's shape.The divergence from floci-aws, worth a reviewer's eye
floci-aws's JVM image is Debian
noble. This repo's iseclipse-temurin:25-jre-alpine, where busyboxchroothas no--userspec:It works only because that image ships GNU coreutils at
/usr/sbin/chroot, ahead of busybox on PATH. Verified by running it, not inferred.Verified on a built runtime image
uid=1001(floci) gid=0(root)through the entrypointgroups=0(root),999, so the by-number supplementary group works with no/etc/groupentry/app/datawritablehealthcheck.shexits 0 on a 200 and 1 on a 500The
~/.oci/configsymlinks now run in the final stage, after the/appcopy, so the copy cannot shadow/app/.oci. The compat image keeps noUSERdirective, as before, and that comment is updated since it named gosu.The real numbers land when CI publishes. This image starts at 72.5 MB rather than gcp's 87.9, so the absolute saving will differ; the compat image will move much less than the native one, as it did in gcp (6% vs 39%), because its OCI CLI and Python layers dominate.
Type of change
fix:)feat:)feat!:orfix!:)OCI Compatibility
No wire protocol, endpoint or emulated behavior changes. Packaging only. The compat suite against a real native build is what this PR's CI is for, and it is the check that matters: the entrypoint change alters how Functions and OKE reach the Docker socket.
Checklist