Skip to content

fix(render): route Docker Command through entrypoint; resilient privilege drop#2

Merged
Ho1yShif merged 2 commits into
mainfrom
fix/render-entrypoint-resilient-privdrop
Jul 7, 2026
Merged

fix(render): route Docker Command through entrypoint; resilient privilege drop#2
Ho1yShif merged 2 commits into
mainfrom
fix/render-entrypoint-resilient-privdrop

Conversation

@Ho1yShif

@Ho1yShif Ho1yShif commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Problem

Every Render deploy of the nanobot template failed at runtime with Exited with status 128 and 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 ENTRYPOINT rather than being appended to it. render.yaml set:

dockerCommand: gateway --config /app/render-config.json

assuming it would become nanobot gateway ... via entrypoint.sh. Render instead ran gateway directly — which is not a binary (it's the nanobot gateway subcommand) — 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

  1. render.yaml — invoke the entrypoint explicitly so it actually runs:
    dockerCommand: /usr/local/bin/entrypoint.sh gateway --config /app/render-config.json
  2. entrypoint.sh — resilient, self-diagnosing:
    • Logs the effective user and each decision (so a failed start is never a silent 128 again).
    • Runs as root → chowns the mounted disk → drops to nanobot via setpriv when the runtime permits it (it does on Render), else runs as root so the app can still write the root-owned disk.
  3. Dockerfile (from the prior commit on this line of work) — USER root + PYTHONUNBUFFERED=1 PYTHONFAULTHANDLER=1 baked in for visible crash output.

Verification (live on Render, before merge)

  • Deploy 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/.
  • Public GET / → 200; health check passing.
  • Locally verified across full-caps and caps-dropped runtimes, both against a root-owned disk.

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

Ho1yShif and others added 2 commits July 7, 2026 11:47
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>
@Ho1yShif Ho1yShif merged commit 0a009ba into main Jul 7, 2026
6 checks passed
@Ho1yShif Ho1yShif deleted the fix/render-entrypoint-resilient-privdrop branch July 7, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant