fix(render): route Docker Command through entrypoint; resilient privilege drop#2
Merged
Merged
Conversation
The prior fix used `setpriv` to drop from root to the nanobot user, but the
Render deploy still exited with status 128 and zero output. Reproduced locally:
`setpriv --reuid` needs CAP_SETUID/CAP_SETGID, and when those are not granted it
fails ("setresuid failed: Operation not permitted") and exits before ever
exec'ing the app — matching the silent pre-Python exit seen on Render.
Make the entrypoint resilient and self-diagnosing:
- Log the effective user at startup (and each decision) so a failed start is
visible in logs instead of a silent 128.
- If already non-root, just run the app.
- If root, chown the mounted disk, then probe whether setpriv can drop
privileges. If it can, drop to nanobot (hardening preserved). If not, run the
app as root so it can still write the root-owned disk.
Verified locally (amd64) against a root-owned tmpfs disk in both runtimes:
full-caps root drops to nanobot; caps-dropped root falls back to root. Both
boot clean, ws://0.0.0.0:8765, GET / = 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render's Docker Command replaces the Dockerfile ENTRYPOINT rather than being
appended to it. render.yaml set `dockerCommand: gateway --config ...` assuming
it would become `nanobot gateway ...` via the entrypoint, but Render ran
`gateway` directly — not a binary (it is the `nanobot gateway` subcommand) — so
the container failed at init ("executable file not found in $PATH") and exited
with the silent status 128. The entrypoint (and all disk/privilege prep) never
ran on any prior deploy.
Invoke the entrypoint explicitly in the Docker Command so it runs, prepares the
mounted disk, and execs the app.
Reproduced locally: `--entrypoint gateway` fails at container init exactly as on
Render; routing through `/usr/local/bin/entrypoint.sh gateway --config ...`
boots clean, drops to nanobot, GET / = 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Problem
Every Render deploy of the nanobot template failed at runtime with
Exited with status 128and zero application logs, in a crash loop.Root cause (confirmed on Render)
The real cause was not disk ownership (the earlier working theory). Render's Docker Command replaces the Dockerfile
ENTRYPOINTrather than being appended to it.render.yamlset:assuming it would become
nanobot gateway ...viaentrypoint.sh. Render instead rangatewaydirectly — which is not a binary (it's thenanobot gatewaysubcommand) — so the container failed at init (exec: "gateway": executable file not found in $PATH) and exited with the silent status 128. The entrypoint never ran on any prior deploy, so none of the disk/privilege logic was ever exercised.Reproduced locally:
docker run --entrypoint gateway …fails at container init exactly as on Render.Fix
render.yaml— invoke the entrypoint explicitly so it actually runs:entrypoint.sh— resilient, self-diagnosing:nanobotviasetprivwhen the runtime permits it (it does on Render), else runs as root so the app can still write the root-owned disk.Dockerfile(from the prior commit on this line of work) —USER root+PYTHONUNBUFFERED=1 PYTHONFAULTHANDLER=1baked in for visible crash output.Verification (live on Render, before merge)
live; logs show[entrypoint] starting…→dropping privileges to nanobot via setpriv→ gateway boot →Git store initialized at /home/nanobot/.nanobot/workspace(disk writable) →WebSocket server listening on ws://0.0.0.0:8765/.GET /→ 200; health check passing.Known non-blocking follow-up
HEAD /→ 502 (the WebSocket server rejects non-GET with a traceback). Pre-existing behavior, unrelated to this fix; noisy but does not affect health checks (which use GET).🤖 Generated with Claude Code