Skip to content

Promote synvya-staging to synvya: low-mem EC2 build optimizations#45

Merged
alejandro-runner merged 5 commits intosynvyafrom
synvya-staging
Apr 29, 2026
Merged

Promote synvya-staging to synvya: low-mem EC2 build optimizations#45
alejandro-runner merged 5 commits intosynvyafrom
synvya-staging

Conversation

@alejandro-runner
Copy link
Copy Markdown
Member

Summary

Promotes the build-time memory optimizations validated on staging to production.

What's in this merge

  • ci(ec2): swap + cargo -j 2 + Docker dep cache for low-mem deploys #43ci(ec2): swap + cargo -j 2 + Docker dep cache for low-mem deploys
    • scripts/ec2-prepare-host.sh: idempotent swap (4 GB) + ~/.cargo/config.toml with [build] jobs = 2
    • Workflow calls the prep script before deploy/QA; passes --build-arg CARGO_BUILD_JOBS=2
    • Dropped -a from docker builder/image prune so referenced layers and images survive between deploys
  • fix(docker): use BuildKit cache mounts for cargo deps #44fix(docker): use BuildKit cache mounts for cargo deps
    • Replaces broken stub-source pattern with RUN --mount=type=cache for /usr/local/cargo/registry, /usr/local/cargo/git, /app/target
    • Artifacts copied out to /artifacts/ before the cache mount disappears; runtime stage COPYs from there
    • Workflow exports DOCKER_BUILDKIT=1 and COMPOSE_DOCKER_CLI_BUILD=1

Validated on staging

Staging deploy succeeded after these changes — t3.medium no longer hangs at rand v0.8.5 or other crates during the cargo release build.

Self-contained on production EC2

The workflow, prep script, and Dockerfile are all merged together. On the first production deploy:

  1. New workflow runs, git pull brings the new files onto the prod EC2.
  2. ec2-prepare-host.sh creates the swapfile + cargo jobs config before any build.
  3. Cold cargo build runs under swap + -j 2 — slower but won't OOM.
  4. Cache mounts populate during this cold build; subsequent code-only deploys hit the warm path.

No manual prep on the production host required.

Test plan

  • Merge → confirm production workflow runs the new `ec2-prepare-host.sh` step
  • Watch first production deploy logs for: `swap enabled at /swapfile`, `wrote [build] jobs=2 to ~/.cargo/config.toml`
  • Confirm cold build completes without hanging on `rand` or other crates
  • Push a follow-up trivial change to `synvya` → confirm warm build is much faster (cache mount reuse)
  • Confirm production health check (`curl http://localhost:3000/health\`) passes
  • Inspect production EC2 after deploy: `swapon --show`, `cat ~/.cargo/config.toml`, `free -h`, `docker system df`

alejandro-runner and others added 5 commits April 29, 2026 12:08
The Synvya EC2 instances run Rust release builds during deploy
(docker compose build) and QA (cargo test). On t3.medium (4 GB
RAM, 2 vCPU) these can OOM, hanging at random crates such as
rand v0.8.5. Swap on the host plus a cargo jobs limit reliably
prevents the OOM, but until now had to be set up manually.

- Add scripts/ec2-prepare-host.sh: idempotent helper that ensures
  a 4 GB /swapfile is active and persisted via /etc/fstab, and
  writes ~/.cargo/config.toml with [build] jobs=2 if missing.
- Call it from deploy-staging, deploy-production, qa-staging,
  and qa-production before the build/test step.
- Pass --build-arg CARGO_BUILD_JOBS=2 to docker compose build so
  the in-container cargo build is also constrained.
- Dockerfile: accept CARGO_BUILD_JOBS as ARG and forward to ENV;
  defaults to empty (no change for contributors with bigger RAM).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two changes that make the warm path of Synvya deploys actually warm:

1. Dockerfile: split the rust-builder stage into a deps-only build
   followed by a real-source build. The first cargo invocation runs
   against stub src files for every workspace member, compiling just
   the ~400 transitive dependencies and caching them as a Docker
   layer. The second invocation overwrites the stubs with real code;
   cargo only recompiles workspace crates, reusing dep .rlibs.

   Net effect: code-only changes no longer trigger a full dependency
   recompile on every deploy.

2. Workflow: drop the `-a` flag from `docker builder prune` and
   `docker image prune`. The `-a` variant deletes referenced build
   cache and tagged images, guaranteeing a cold build every time.
   The non-`-a` variants still clean up dangling images and
   unreferenced build cache for disk hygiene.

Combined with the earlier swap + cargo -j 2 commit, this should
let the t3.medium handle warm builds quickly and survive cold
builds without OOMing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ci(ec2): swap + cargo -j 2 + Docker dep cache for low-mem deploys
…che mounts

The previous attempt at a deps-cache stage (stub every workspace
member's source, build deps with stubs, then COPY real source)
fails at compile time because the Keycast binary's dependency
graph crosses workspace boundaries: keycast → keycast_api →
keycast_core::types, etc. Stubbing keycast_core's lib.rs to empty
makes keycast_api fail to compile inside Stage A.

The stub pattern only works for projects with no workspace-internal
deps. Keycast has them everywhere.

Fix: collapse to a single rust-builder stage and use BuildKit
cache mounts for /usr/local/cargo/registry, /usr/local/cargo/git,
and /app/target. The mounts persist on the host's BuildKit
instance across builds, so unchanged dep crates stay compiled
without any source-stubbing trickery. Built artifacts are copied
out of target/ to /artifacts/ before the RUN ends, since cache
mounts are not part of the resulting image layer; the runtime
stage now COPYs from /artifacts/.

Workflow exports DOCKER_BUILDKIT=1 and COMPOSE_DOCKER_CLI_BUILD=1
to be explicit (compose v2 enables BuildKit by default).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(docker): use BuildKit cache mounts for cargo deps
@alejandro-runner alejandro-runner merged commit 9893eba into synvya Apr 29, 2026
5 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.

1 participant