Install function dependencies independently of HOME#77
Conversation
pip install --user resolves to $HOME/.local, which only works while the container runs as the user the image declares. OpenFaaS can be installed with functions.setNonRootUser=true, which pins runAsUser to 12000 and overrides that USER. A uid with no passwd entry gets HOME=/, so the packages are no longer on the import path and the function crash-loops: ModuleNotFoundError: No module named 'requests' It fails in the cluster only. local-run and docker run both honour the image's own USER, so the image passes every local test and CI, and the first sign of trouble is a CrashLoopBackOff. A Profile setting runAsUser, and OpenShift's per-namespace uid ranges, break it the same way. Install to /home/app/python instead, which no uid can move, and add it to the path. This is what the classic python3 template has always done. Dependency shadowing is unchanged: the directory still precedes site-packages, so a function may pin its own version of something the template also installs. index.py registers the directory with site.addsitedir rather than relying on PYTHONPATH alone, because .pth files are only processed for site directories. Without it, a dependency shipping a .pth would stop working for every build, not just the ones this change fixes. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
AI Pull Request OverviewDisclaimer: This review was generated by automated AI and may contain errors. Do not trust its outputs without human verification. Summary
Approval rating (1-10)9/10. The fix is focused, consistent, and addresses the arbitrary-uid dependency lookup failure without introducing an evident regression. Summary per fileSummary per file
Overall AssessmentThe change directly addresses the reported arbitrary-uid runtime failure by removing dependency discovery from Detailed ReviewDetailed ReviewNo findings. The reviewed Dockerfile changes are narrowly scoped to the function dependency install step and matching runtime path environment. The AI agent details. |
The bug
A function with any dependency of its own crash-loops on a cluster installed with
functions.setNonRootUser=true:pip install --userresolves to$HOME/.local/..., which only works while the container runs as the user the image declares.setNonRootUserpinsrunAsUser: 12000, overridingUSER app. A uid with no/etc/passwdentry getsHOME=/, so Python looks in/.local/...while pip installed into/home/app/.local/...:It fails in the cluster only.
local-runanddocker runhonour the image's ownUSER, so the image passes every local test and CI. The first sign of trouble is aCrashLoopBackOff. A Profile settingpodSecurityContext.runAsUser, and OpenShift's per-namespace uid ranges, break it identically.Reproduce without a cluster:
The change
Install to a fixed path that no uid can move, and add it to the path — which is what the classic
python3template intemplates-classichas always done, comment and all ("Allow any user-id for OpenShift users"):index.pyalso registers the directory withsite.addsitedir(). That matters:.pthfiles are only processed for site directories, never for barePYTHONPATHentries, so without it a dependency shipping a.pthwould silently stop working for every build — a wider blast radius than the bug being fixed. Verified withsetuptools, which shipsdistutils-precedence.pth.Only the function's own
requirements.txtchanges. The template's own dependencies still go in as root, system-wide, untouched.Who this affects
Fixed: clusters with
setNonRootUser=true, any Profile pinningrunAsUser, and OpenShift — where a function has at least one dependency. An emptyrequirements.txtmasks the bug entirely, because the template's own deps are installed as root and survive.No change: default installs (
setNonRootUserdefaults tofalse), and functions with no dependencies. Nobody needs to touchstack.yamlor handler code; the fix applies on the nextfaas-cli build. Deployed images are unaffected until rebuilt.Testing
Adversarially reviewed, then verified per template. Built old vs new images and ran them:
--user)--target)USER--user 12000--user 12100--user 65534--read-only --tmpfs /tmp.pthprocessedaddsitedir)Also checked: console scripts still land in
bin/and are onPATH; dependency shadowing is unchanged, with a function-pinnedflask==2.2.5still winning over the template's own copy at uid 12000;cryptographyC extensions import correctly on alpine and debian and cross-build for arm64;python27-flaskbuilds on pip 20 / Python 2.7 and the fix works there too;TEST_ENABLED=truetox/pytest/flake8 passes; image size unchanged. The function was run end to end under uid 12100 and served a request. TheUndefinedVarbuildkit warning is gone, sincePYTHONPATHwas previously prefixed with a$PYTHONPATHthat is never set — which was putting an empty entry onsys.path.Secondary improvement:
importlib.metadatalookups now work at any uid; with--userthey failed entirely under uid 12000 withNo package metadata was found.