Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeshammz

Relationships

#2652 Docker image runs swamp as PID 1 without an init, so orphaned step processes are never reaped

Opened by hammz · 9/28/2026· Shipped 9/29/2026

Summary

The official swamp image runs swamp as PID 1 with no init, so orphaned child processes are never reaped. Every process a step leaves behind stays a zombie until the container exits.

Dockerfile builds FROM denoland/deno:2.9.7, whose entrypoint is ["/tini", "--", "docker-entrypoint.sh"], and then sets ENTRYPOINT ["swamp"]. That override drops tini. Linux re-parents orphans to PID 1, and Deno only waits on children it spawned itself through Deno.Command, so swamp never collects adopted orphans.

Reproduction

A command/shell step that backgrounds a short-lived process, waits for it to finish, then lists zombies (step.sh):

(sleep 1 &)
sleep 3
echo "PID 1 is: $(tr '\0' ' ' < /proc/1/cmdline)"
for f in /proc/[0-9]*/status; do
  grep -q '^State:.*Z' "$f" 2>/dev/null && echo "ZOMBIE: $(awk '/^(Name|Pid|PPid):/ {printf "%s %s  ", $1, $2}' "$f")"
done

Run inside denoland/deno:2.9.7 with swamp as PID 1 (the image's layout), and again with /tini as PID 1:

Container entrypoint PID 1 Zombies after the orphan exits
swamp image (ENTRYPOINT ["swamp"]) swamp model method run z execute sleep, Pid 33, PPid 1
base image's /tini /tini -- sh /demo/setup.sh none

Impact

  • A long-running swamp serve container builds up zombies from every step that leaves a process behind, for as long as it runs, using up PIDs and process-table slots.
  • After swamp-club#2634, a killed step's process group is signalled as a whole. sh dies without collecting its children, so every descendant below the group leader is handed to swamp and becomes a zombie, unless an intermediate parent handled SIGTERM and waited for its children. The group leader itself is always collected by Deno.
  • A zombie still counts as a process-group member, so #2634's liveness probe keeps seeing the group as alive. Every cancel inside a container therefore waits the full 3 s grace before its SIGKILL (a no-op on zombies).
  • The same applies under Kubernetes, where each container has its own PID namespace. docker run --init is not available there, so the fix belongs in the image.
  • PID 1 also gets no default signal dispositions: any signal swamp does not handle is ignored instead of terminating it.

Proposed fix

  1. Dockerfile: ENTRYPOINT ["/tini", "--", "swamp"]. /tini (v0.19.0) already ships in the base image. It forwards docker stop's SIGTERM to swamp, which handles it as today, and it passes swamp's exit code through. docker run <image> serve ... is unchanged. Confirm /tini is present in the arm64 variant too, since release.yml builds amd64 and arm64.
  2. Pin it: add a check to integration/toolchain_pins_rules_test.ts, in the same ratchet style as the FROM pin, asserting the ENTRYPOINT starts with /tini, so it cannot be reverted to ["swamp"] silently.
  3. Warn: in the swamp serve and swamp worker connect actions, when running on Linux as PID 1, log a warning that swamp is running as PID 1 without an init and exited children will not be reaped, and suggest --init or tini. This covers users who copy the binary into their own images. The predicate needs a small unit test.
  4. Docs: note in design/primitives/workflows.md (process-tree termination section) that a container needs an init, and that the image ships one. Also update the manual's swamp-serve page if it describes running the image.

Not proposed: collecting adopted zombies inside swamp through libc waitpid FFI. A blanket waitpid(-1) would steal exit statuses from Deno's own Deno.Command waits. A targeted version would be Linux-specific, and it would still miss orphans from steps that exited normally.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 11 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/29/2026, 3:00:40 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
hammz assigned hammz9/29/2026, 2:19:47 PM

Sign in to post a ripple.