Run the release image as uid 65532 - #374
Open
schronck wants to merge 3 commits into
Open
Conversation
The runtime image was built on gcr.io/distroless/cc-debian12 with no USER, so the container ran as uid 0. The binary never needed it: it binds 8080 and 9000, both above 1024, and writes only under its data directory. Switches the base to the :nonroot tag, which carries uid/gid 65532 and a home directory it owns, and states USER 65532:65532 explicitly so the runtime identity is visible in this file rather than inherited. DECPM_DIR moves from / to /home/nonroot. It is the ROOT dir and the app writes $DECPM_DIR/data, so the old default put the database at /data, which uid 65532 cannot create. Deployments override it anyway, but a plain docker run now works instead of failing on the first write. The deployment guide gains a pod securityContext with runAsNonRoot, runAsUser/runAsGroup 65532 and fsGroup 65532 on the data volume, plus allowPrivilegeEscalation false and all capabilities dropped on the container. The init container inherits the pod context: busybox defaults to root, and with runAsNonRoot set the pod would refuse to start otherwise. fsGroup is what lets both it and the app write to the mounted PVC. readOnlyRootFilesystem is left commented rather than set. It should hold, but it has not been proven against a live deploy and a dependency writing to /tmp would surface only at runtime. Closes #372
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens the runtime container by running the release image as a non-root user (uid/gid 65532) and updates the Kubernetes deployment documentation to match the tightened security posture and filesystem ownership requirements.
Changes:
- Switch the runtime base image to
gcr.io/distroless/cc-debian12:nonrootand explicitly setUSER 65532:65532. - Change the runtime default
DECPM_DIRto/home/nonrootso the non-root process can create itsdata/directory by default. - Update
docs/DEPLOYMENT_GUIDE.mdto add pod/container securityContext settings (runAsNonRoot,runAsUser,fsGroup, drop caps, etc.) and document the init-container interaction.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
docs/DEPLOYMENT_GUIDE.md |
Documents non-root runtime behavior and updates the Deployment example to use pod/container security contexts appropriate for uid 65532. |
Dockerfile |
Moves the runtime image to distroless :nonroot, sets USER 65532:65532, and updates DECPM_DIR defaults for non-root writes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The example mounted ./data at /data, which worked only because DECPM_DIR defaulted to / and the app writes $DECPM_DIR/data. With the nonroot default at /home/nonroot the container would write inside its own filesystem and the mount would sit unused, so the data disappeared with the container without any error. Also adds the chown the nonroot image needs: a bind mount keeps the host's ownership, so uid 65532 cannot write to it otherwise. Review finding from Copilot on #372.
Same problem as the USER_GUIDE example: ./data mounted at /data worked only under the old DECPM_DIR=/ default. Copilot flagged the USER_GUIDE one; grepping for the pattern found this one too. Review finding from Copilot on #372.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The runtime image had no
USER, so the container ran as uid 0. The binary never needed it: it binds 8080 and 9000, both above 1024, and writes only under its data directory.The image
Base moves to
gcr.io/distroless/cc-debian12:nonroot. I read the image config from the registry rather than trusting the tag name:USER 65532:65532is also stated explicitly in the Dockerfile. The base already sets it, but stating it keeps the runtime identity visible in the file and surviving a base-image change.One thing the issue did not mention, and it would have broken
docker runDECPM_DIRdefaulted to/. It is the root dir, and the app writes$DECPM_DIR/data— so the default put the database at/data, which uid 65532 cannot create. Switching the base without touching this would have left the image failing on its first write for anyone running it directly.It now defaults to
/home/nonroot, which the nonroot user owns. Deployments are unaffected either way: the guide mounts a volume and passes-d /app, anddevelopment/docker-compose.ymlbuilds from a different Dockerfile and setsDECPM_DIR: /appitself.The deployment guide
Pod-level:
Container-level:
allowPrivilegeEscalation: falseandcapabilities: drop: ["ALL"].Two details worth a reviewer's eye:
busyboxdefaults to root, and withrunAsNonRoot: trueat pod level the pod refuses to start unless that container also runs nonroot. It inherits the pod context, andfsGroupis what then lets both it and the app write to the mounted PVC. WithoutfsGroupthis change breaks the deploy.readOnlyRootFilesystemis left commented, not set. It should hold — every/tmpandtempfileuse I found is test code or thegen-typesbinary, not the server — but I cannot prove it without a live deploy, and a dependency writing to/tmpwould surface only at runtime. The guide says to enable it and watch one restart.Also documented: the image runs as 65532, and the workaround for v1.6.2 and earlier, which is to set
runAsUser: 65532plus the samefsGroupon the existing image.Verification
No docker in this environment, so I could not build the image. What I did check:
nonrootimage config, pulled fromgcr.io(above), confirming uid and home.The image itself is unverified by a build. The remaining risk is the base-image swap, which CI will exercise on this PR.
Follow-up the issue asks for and this does not do
That is a tag push, not a code change, so it stays with whoever cuts releases. The OS-level CVEs the scanners report come from the base image and clear on a rebuild.
Closes #372