From 1c2146dc823993f2399fcf99a57cd4d5ec00cfab Mon Sep 17 00:00:00 2001 From: JJ Eaton <58495321+jayleaton@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:05:21 +0700 Subject: [PATCH] fix(backups): GOMEMLIMIT so a two-database backup run fits the 512 MiB cap Each database's age scrypt step (work factor 18) allocates ~256 MiB that is garbage right after; without a soft limit the GC let the second database's allocation stack on the first and the 512 MiB container was OOM-killed (exit 137) after app/ was stored, killing the service with it. GOMEMLIMIT=384MiB makes the GC reclaim first. Co-Authored-By: Claude Opus 5.5 --- apps/backup/Dockerfile | 6 ++++++ deploy/platform/graph.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/backup/Dockerfile b/apps/backup/Dockerfile index c7a3dbf..3021a91 100644 --- a/apps/backup/Dockerfile +++ b/apps/backup/Dockerfile @@ -16,6 +16,12 @@ RUN CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /out/backup ./apps/back FROM postgres:18-alpine COPY --from=build /out/backup /usr/local/bin/backup ENV APP_ENV=production +# Each database's age scrypt step (packages/backup, work factor 18) allocates +# ~256 MiB that is garbage right after. Without a soft limit the GC lets the +# next database's allocation stack on it, and the 512 MiB container cap +# (deploy/platform/graph.go) kills the run on the second database. The limit +# makes the GC reclaim first; keep it below that cap. +ENV GOMEMLIMIT=384MiB USER postgres WORKDIR /tmp # The postgres image's entrypoint would start a server: reset it. People run diff --git a/deploy/platform/graph.go b/deploy/platform/graph.go index 711d5a4..0678f78 100644 --- a/deploy/platform/graph.go +++ b/deploy/platform/graph.go @@ -260,7 +260,7 @@ func (config Config) backups(env string, appURL, adminURL map[string]any) []any if adminURL != nil { vars["ADMIN_DATABASE_URL"] = adminURL } - const backupMemoryMB = 512 // pg_dump of a large table + const backupMemoryMB = 512 // pg_dump of a large table; apps/backup/Dockerfile GOMEMLIMIT stays below it sc := railway.ServiceConfig{ "build": map[string]any{ "builder": "DOCKERFILE",