Skip to content

fix(deploy): build binaries on the runner to stop droplet OOM (exit 255)#27

Merged
ralyodio merged 1 commit into
mainfrom
fix/deploy-build-on-runner
Jun 15, 2026
Merged

fix(deploy): build binaries on the runner to stop droplet OOM (exit 255)#27
ralyodio merged 1 commit into
mainfrom
fix/deploy-build-on-runner

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Problem

The deploy run for #26 failed at the build step with:

==> building binaries (go build -p=1 to cap peak memory)
Connection to *** closed by remote host.
##[error]Process completed with exit code 255.

exit 255 + "Connection closed by remote host" is an SSH-level disconnect: the deploy SSHes into the ~458MB droplet and runs go build there, but the Go linker's peak memory OOM-kills the build — and the kernel takes out the sshd serving the deploy session with it. It's flaky because it tracks momentary memory pressure from the co-resident ergo / forgejo / tor / podman / live-agentbbs processes (run #25 passed, #26 failed 6 min later on near-identical code).

setup.sh already anticipated this (SKIP_BUILD env, "tiny droplets can't compile") but the deploy never used it.

Fix

  • Build both binaries on the 16GB GitHub runner, not the droplet. They're pure-Go (modernc.org/sqlite, no cgo), so CGO_ENABLED=0 produces a static, portable binary.
  • Detect the droplet's arch (uname -mamd64/arm64) so the cross-build matches.
  • scp the binaries over, then run setup.sh with SKIP_BUILD=1 — the box never compiles, removing the OOM failure mode entirely.
  • setup.sh now also skips the Go toolchain download when SKIP_BUILD=1.

setup.sh still does its git reset --hard origin/<branch> so config/assets/service definitions stay in sync; only the compile step is offloaded.

Notes / follow-up

  • Verified locally that the modules are pure-Go (no import "C"); the actual cross-build runs on the runner via actions/setup-go + go-version-file: go.mod (1.26.x). The sandbox here only has Go 1.22, so the compile itself is validated by this workflow run.
  • The 15-min self-update systemd timer on the box still runs setup.sh with the default SKIP_BUILD=0 and would compile on-box. Out of scope here; can be switched to a "fetch prebuilt binary from CI artifacts" model in a follow-up if its on-box builds also prove flaky.

🤖 Generated with Claude Code

…n box

The deploy SSHed into the ~458MB droplet and ran `go build` there. The Go
linker's peak memory OOM-killed the build — and with it the sshd serving the
deploy session — surfacing as "Connection closed by remote host" (exit 255).
It was flaky because it tracked momentary memory pressure from the co-resident
ergo/forgejo/tor/podman/agentbbs processes (run #25 passed, #26 failed on
near-identical code).

Build both binaries on the 16GB GitHub runner instead (pure-Go, modernc
sqlite, so CGO_ENABLED=0 static cross-build), scp them to the droplet, and run
setup.sh with SKIP_BUILD=1 so the box never compiles. Arch is detected from
the droplet so amd64/arm64 both work. setup.sh now also skips the Go toolchain
download when SKIP_BUILD=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio ralyodio merged commit d763d73 into main Jun 15, 2026
5 checks passed
@ralyodio ralyodio deleted the fix/deploy-build-on-runner branch June 15, 2026 14:10
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.

1 participant