Skip to content

Start the box image under tini - #97

Merged
splitbrain merged 1 commit into
splitbrain:mainfrom
kossmac:box-image-tini
Oct 6, 2026
Merged

splitbrain merged 1 commit into
splitbrain:mainfrom
kossmac:box-image-tini

Conversation

@kossmac

@kossmac kossmac commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Split out of #94, as suggested there.

What changed

  • box-image/Dockerfile installs tini (Ubuntu's package, /usr/bin/tini, 0.19.0) and starts the entrypoint under it: ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"].
  • docker.ts drops Init: true for the box container and the login container. Both run the image's entrypoint, so tini is their PID 1 now.
  • The root helper in oneShot keeps Init: true, because it replaces the entrypoint and tini with it.
  • ARCHITECTURE.md says where the init comes from now.
  • background.test.ts has a case for a box held open under tini. BOX_INIT already matched tini, so the gateway needs no change.

The two changes have to go together. With Init: true and tini in the entrypoint, tini is not PID 1, and without -s it reaps nothing.

Why

The init is now part of the image, so anything that runs the image gets it, not only a caller that knows to ask Docker for one. Without an init, the entrypoint's sleep is PID 1. It never sees SIGTERM, and it never reaps the processes orphaned in the box.

Compatibility

A container keeps the HostConfig it was created with. A box created before this change keeps Init: true and the old image until it is recreated. When the image changes, the box is recreated with both the new image and no Init. A deployment that pins BOX_IMAGE to an image from before this change, with a newer orchestrator, would run its boxes without an init.

Testing

  • npm run check and npm test in orchestrator. One test, review/fs.test.ts, fails on macOS because of the /var → /private/var symlink. It fails the same way on main.
  • I did not build the whole image. Instead I put the change as a thin layer on ghcr.io/splitbrain/boxes/box:latest (apt-get install tini and the new ENTRYPOINT) and ran it without --init, with a read-only root filesystem and --cap-drop ALL:
    • PID 1 is /usr/bin/tini -- /usr/local/bin/entrypoint.sh, with sleep infinity as its child.
    • A process orphaned by docker exec is reaped. The current image without an init leaves it as a zombie.
    • docker stop takes 0.1 s, against 3.1 s for the current image without an init.

🤖 Generated with Claude Code

The box and login containers got their init from Docker's `Init: true`.
Anything else that runs the image has to know to add one, or the
entrypoint's `sleep` is PID 1: it ignores SIGTERM, so every stop waits
out the grace period, and it reaps nothing, so every process orphaned in
the box stays a zombie and counts against the pids limit.

The image now installs tini and starts its entrypoint under it, and the
box and login containers drop `Init: true`. Both have to change
together: under docker-init, tini is not PID 1 and reaps nothing
without `-s`. The root helper in `oneShot` replaces the entrypoint, so
it keeps `Init: true`. The gateway already reads `tini` as a box's init.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@splitbrain
splitbrain merged commit 841a260 into splitbrain:main Oct 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants