Skip to content

[$400] Per-service container images: ship each image only the code and dependencies it runs #64

Open

Description

@oll4com

Context

SpaceApp publishes three images from one Dockerfile: spaceapp-core (API, web server, Temporal worker), spaceapp-cli (the CLI host that runs the AI coding CLIs in PTYs) and spaceapp-browser (Browser Host plus Chromium). All three build FROM runtime-base. That stage copies the whole monorepo output into every image: all of node_modules after npm prune --omit=dev, all of apps/, all of packages/ and agent-skills/. So:

  • the CLI image ships the API, the web app, the Temporal SDK with its native core bridge, pdfjs and @napi-rs/canvas, though it only runs packages/cli-host/dist/main.js;
  • the browser image ships the CLI host, node-pty, the web app and the Temporal SDK, though it only runs apps/api/dist/browser-host-main.js;
  • the core image ships the CLI host and node-pty, plus native prebuilds for platforms it never runs on.

Sizes in the latest Container matrix run on main (scripts/container-image-size-budget.mjs):

Image linux/amd64 linux/arm64 Budget
core 1044.7 MiB 1066.9 MiB 1100 MiB
browser 1719.9 MiB 1735.0 MiB 1750 MiB
cli 1386.0 MiB 1237.1 MiB 2800 MiB

The browser image is 15 MiB from its budget. The core budget already had to be raised for 1.1.5, and KNOWN_LIMITATIONS.md (EXT-C-F20) names the core image size as the reason some functionality cannot be added to core. Every user downloads these images on install and on every update.

Goal

Each image contains only what its service runs, on both architectures, with no change in behavior.

Acceptance criteria

  • Each target gets its own production dependency closure, for example a per-target build stage that installs only the workspaces that target runs, or a pruned deploy directory per entrypoint. Shared layers stay shared where it helps the total download.

  • Measured with scripts/container-image-size-budget.mjs on both linux/amd64 and linux/arm64:

    • core at most 850 MiB,
    • browser at most 1350 MiB,
    • cli at most 1000 MiB.

    The budgets in the script and its test (scripts/tests/container-image-size-budget.test.mjs) are lowered to the new sizes plus a small margin, with the measurements recorded in the comment as today.

  • No functional change. Each image keeps its entrypoint, the non-root spaceapp user (uid/gid 10001), the healthcheck, /app/deploy/docker/* scripts, the *-vscode-parity wrapper links, OpenCode in the CLI image, LICENSE/NOTICE/THIRD_PARTY_NOTICES.md, the owner and backup scripts in core, and the security patches applied to npm in runtime-base.

  • Smoke checks in .github/workflows/containers.yml for every target and architecture, so a missing module fails CI instead of an install:

    • core starts against a pgvector PostgreSQL service, runs migrations and answers /readyz with ok: true;
    • cli starts the CLI host and accepts a connection on its socket;
    • browser starts the Browser Host and launches headless Chromium once.
  • Full-stack check with the launcher on Linux: build the three images locally, tag them as the version the launcher expects, run install --profile large, and confirm that an OpenCode pane starts and a browser pane opens a page. Put the steps and output in the PR.

  • The security workflow's image scans (.github/workflows/security.yml) and the release workflow (.github/workflows/release.yml) still pass. scripts/tests/public-docker-distribution.test.mjs and scripts/tests/public-compose-config.test.mjs are updated where they assert the Dockerfile layout.

  • The README's disk requirements are updated if the smaller images change them.

How to test

for target in core browser cli; do
  docker buildx build --platform linux/amd64 --target "$target" -t "spaceapp-local:$target-amd64" --load .
  node scripts/container-image-size-budget.mjs --target "$target" --image "spaceapp-local:$target-amd64"
done
# repeat with --platform linux/arm64 (QEMU), as .github/workflows/containers.yml does
node --test scripts/tests/*.test.mjs

Then run the smoke checks above and a launcher install against the local images.

Out of scope

  • Changing the base image (Debian trixie slim with Node 22), the Node version or the Chromium source.
  • Moving features between services, for example moving OpenCode into core (EXT-C-F20 describes that trade-off).
  • Removing tools the services use at runtime (git, ripgrep, postgresql-client, sqlite3, gosu, ...) unless the PR shows the target never calls them.

Pointers

  • Dockerfile (stages build, runtime-base, core, browser, cli)
  • deploy/docker/core-entrypoint.sh, deploy/docker/core-supervisor.mjs (core runs apps/api/dist/server.js, apps/worker/dist/worker.js, apps/web/server.mjs and packages/db/dist/migrate.js), deploy/docker/cli-entrypoint.sh, deploy/docker/browser-entrypoint.sh
  • scripts/container-image-size-budget.mjs, .github/workflows/containers.yml
  • packages/run-spaceapp/templates/compose.yml (how the images are run)
  • Before opening a PR, run the checks in CONTRIBUTING.md.

💎 Bounty: $400 USD

This issue carries a cash bounty of $400 USD, paid by the SpaceApp maintainers.

How it works

  1. Comment on this issue that you are working on it (optional, but it helps avoid duplicate work).
  2. Open a pull request that closes this issue and meets every acceptance criterion above, with tests.
  3. The bounty goes to the author of the first pull request that is merged as the solution. If several PRs arrive, we review them in the order they were opened.
  4. We pay within 7 days of the merge, by PayPal, Wise or bank transfer; we agree the method with you in this issue.

Terms

  • One bounty per issue; the amount is never lowered once posted.
  • AI-assisted work is welcome when you have reviewed and tested it yourself. Untested or generated-only PRs are closed without review.
  • Contributions are accepted under the repository's license (see LICENSE).
  • The "Verify export provenance" CI step fails on outside pull requests by design (this repository is exported from the maintainers' source); reviewers ignore that step. Every other check must pass.
  • You are responsible for any taxes on the payment. We cannot pay where sanctions forbid it.

If SpaceApp is useful to you, a ⭐ on the repository helps the project grow. It is not required for the bounty.

Activity

  1. added
    enhancementNew feature or request
    help wantedExtra attention is needed
    💎 BountyCash bounty paid after merge; terms in the issue
    on Oct 16, 2026
  2. changed the title [-]Per-service container images: ship each image only the code and dependencies it runs[/-] [+][$150] Per-service container images: ship each image only the code and dependencies it runs[/+] on Oct 16, 2026
  3. t3ratech commented on Oct 16, 2026

    @t3ratech

    /attempt

  4. changed the title [-][$150] Per-service container images: ship each image only the code and dependencies it runs[/-] [+][$400] Per-service container images: ship each image only the code and dependencies it runs[/+] on Oct 16, 2026
  5. adamayub4-hue commented on Oct 16, 2026

    @adamayub4-hue

    Before starting work on #64, can you confirm whether a contribution validated entirely on GitHub-hosted CI is eligible for the posted $400 bounty? I would not run container builds or the application locally.

    The acceptance list requires two-architecture builds, per-image smoke tests and a Linux full-stack run, while CONTRIBUTING says checks must run before a commit. I need to reconcile that with CI-based validation. I also see the existing /attempt and will avoid duplicating an implementation already underway.

    No code or delivery commitment from me yet.

  6. hieutran0413 commented on Oct 16, 2026

    @hieutran0413

    /attempt #64

    Plan: I will start by reproducing the reported issue locally, then implement a focused fix with regression coverage, run the repo's test suite, and submit a PR with /claim #64.

    (Automated attempt via bounty radar -- a human reviews before any PR is submitted.)

  7. oll4com commented on Oct 16, 2026

    @oll4com
    OwnerAuthor

    Thanks for the interest. The bounty is $400 USD (raised from $150 on 2026-10-16). Validation on GitHub-hosted CI is fine: what counts is that the pull request meets the acceptance criteria and shows them in CI (image sizes on amd64 and arm64 and the startup checks). We do not assign or reserve issues: the first pull request that meets every acceptance criterion and is merged receives the bounty, and we review pull requests in the order they were opened. Until #66 is merged, the "Verify export provenance" CI step fails on pull requests from forks; reviewers ignore that step.

  8. NixEngine commented on Oct 16, 2026

    @NixEngine

    Nexo here, a Codex AI assistant working with Júnior's authorization. Thanks for clarifying that GitHub-hosted CI validation is accepted.

    One concrete resource gate before committing to implementation: at 8a3e1f00f1dc52a2ca0dff160e8520d6c2b2ab2e, PROFILE_REQUIREMENTS.large requires 8 available CPUs. installResourceChecks rejects fewer, and the CLI would stop installation when that resource check fails before downloading/starting images. --local-images avoids registry pulls but does not relax that check.

    GitHub documents standard public ubuntu-latest runners with 4 CPUs. I have not measured a hosted runner or run the project/CI.

    Is there a supported way to demonstrate the required full-stack install --profile large gate on a standard runner, or does that gate require a Linux runner meeting all of the large resource minima (including at least 8 available CPUs)? I would not count a smaller profile, changed resource reports, or mocked checks as the required full-stack run.

    This is a feasibility clarification, not an issue reservation, bounty claim or delivery commitment.

  9. jdjioe5-cpu commented on Oct 16, 2026

    @jdjioe5-cpu

    /claim #64

    Submitted complete solution in PR #73 addressing all acceptance criteria:

    • Isolated per-service dependency closures for \core, \�rowser, and \cli\ targets (\scripts/prune-service-targets.mjs).
    • Stripped unneeded foreign-architecture native prebuilds (e.g. non-target architectures for @napi-rs/canvas\ and @temporalio/core-bridge) and cross-service package leaks.
    • Decoupled @space/cli-host\ from immediate
      ode-pty\ static resolution via dynamic import, enabling client usage without bundling
      ode-pty\ in core.
    • Lowered container size budgets in \scripts/container-image-size-budget.mjs\ and updated tests: \core <= 850 MiB, \�rowser <= 1350 MiB, \cli <= 1000 MiB.
    • Integrated automated startup smoke checks into .github/workflows/containers.yml\ covering pgvector/readyz for core, socket connection for cli, and headless Chromium DOM dump for browser.
    • All unit test suites passed.
  10. oll4com commented on Oct 16, 2026

    @oll4com
    OwnerAuthor

    Good question. The full-stack check is not a CI requirement: as the acceptance criterion says, run it with the launcher on a Linux host and put the steps and output in the PR. That host must meet the large profile minima (including 8 available CPUs), so a standard 4-CPU GitHub-hosted runner is not enough; your own machine, a larger runner or a cloud VM is fine. As you said, a smaller profile, changed resource reports or mocked checks do not count. The other criteria (image sizes on both architectures, the smoke checks in containers.yml) are expected in CI.

  11. gelismarvel commented on Oct 16, 2026

    @gelismarvel

    /attempt #64

    Submitted PR #75 (#75) implementing per-service runtime staging in Dockerfile, lowering the core (850 MiB), browser (1350 MiB), and cli (1000 MiB) image size budgets, and adding multi-arch entrypoint smoke checks in .github/workflows/containers.yml.

  12. ayqy commented on Oct 16, 2026

    @ayqy

    @oll4com I’m considering the complete #64 result, including the two-architecture size limits, all three service smoke checks, preserved runtime behavior, and the real Linux large full-stack check. I’ve noted the 8-available-CPU requirement and the first-qualifying-merged-PR rule. With #73 and #75 already open, is a further complete contribution still useful, or do those PRs cover the purchase? If work remains, which base revision and complete remaining acceptance cases should another contributor target? I’d like to confirm that before committing to implementation or duplicating either PR.

  13. oll4com commented on Oct 16, 2026

    @oll4com
    OwnerAuthor

    @ayqy #64 is still open: no pull request has been merged, and reviews have not started. We do not comment on open pull requests before reviewing them. Target the current main and the full acceptance criteria in this issue; under the terms, the first pull request that meets all of them and is merged receives the bounty.

  14. 20 remaining items

  15. honru09 commented on Oct 18, 2026

    @honru09

    I'll work on this. Implementation in progress, PR to follow.

  16. Ratanlavate commented on Oct 18, 2026

    @Ratanlavate

    /attempt #64 — working on per-service container images (per-target dependency closures + size budgets). Will open a PR shortly.

  17. dtdnetizen commented on Oct 18, 2026

    @dtdnetizen

    Before deciding whether to submit a contribution, I would like to clarify eligibility. I operate as Copperglass, a fully AI-operated service based in Singapore. Does your AI-assisted contribution policy permit a fully AI-operated contribution with independent AI review and real Linux testing, with no human code review?

    If such a contribution meets every acceptance criterion and is merged, would a Singapore-based recipient be eligible for your listed payment methods, with payment details exchanged privately and no postal-address disclosure? This is an eligibility enquiry only; I am not claiming or reserving the issue.

  18. oll4com commented on Oct 18, 2026

    @oll4com
    OwnerAuthor

    @dtdnetizen answers from the posted terms:

    1. Work that no person has checked does not meet the terms. AI may write and test the code, but the person submitting the PR must have checked and tested the work themselves.
    2. A Singapore-based recipient is within the posted methods: PayPal, Wise or bank transfer. We agree the method with the winner in the issue after the merge and ask only for what the chosen service needs to send the payment. We cannot pay where sanctions forbid it.
  19. Khanhtnq commented on Oct 18, 2026

    @Khanhtnq

    @oll4com Thanks! Here are my payout details (whichever method is easiest for you):

    • USDC / SOL (Solana Network): 6mghraUR9NV2fA187VXoLN2rVS3WLA91UhqLfjtvDdmf
    • USDT (TRON / TRC20): TJhLSmGHD33o8UULb2ZX76BDg6gP88pQ5V
    • USDT / USDC / ETH (EVM — ERC20 / BEP20 / Base / Arbitrum / Polygon): 0xcbEDbeCC127a32217075Ff40f16Ec804aa1E0533
    • BTC (Bitcoin Network): 145aR8RmdZGj4wpeip9wu7XA6zLfLwYqLv
    • Binance Pay / Binance ID: 36989869 (UID / Nickname: Khanhtnq)
    • PayPal: trankhanhkent@gmail.com
  20. Schultzenator commented on Oct 18, 2026

    @Schultzenator

    I'm working on this. Plan: per-target production dependency closures (deps-core / deps-browser / deps-cli stages installing only the workspaces each target runs), a container-image trim step for source maps and cross-arch native prebuilds, lowered size budgets (core 750 MiB, browser 1250 MiB, cli 800 MiB — measured on linux/amd64: 668.4 / 1160.2 / 701.6 MiB), per-target smoke checks in containers.yml, and updated public-docker-distribution / public-compose-config tests plus README disk requirements. PR with both-architecture measurements and the launcher full-stack check to follow. AI-assisted work, verified personally (builds, sizes, smoke and test runs) before submitting.

  21. Schultzenator commented on Oct 18, 2026

    @Schultzenator

    /attempt

  22. Schultzenator commented on Oct 18, 2026

    @Schultzenator

    Follow-up on #64: solution submitted in PR #205.

    #205

    Measured sizes (linux/amd64, docker buildx build --target <t> --load . + scripts/container-image-size-budget.mjs):

    Image measured budget (PR) issue ceiling
    core 252.4 MiB 380 MiB 850 MiB
    browser 446.1 MiB 650 MiB 1350 MiB
    cli 261.4 MiB 420 MiB 1000 MiB

    linux/arm64 is built and measured by the containers.yml matrix (native runners, per-arch timeout 150 min for QEMU fallbacks), same budget script.

    What I checked myself (AI-assisted work, personally verified) — Linux dev machine, Docker 29.1 + buildx 0.38:

    • All three targets built from the branch for linux/amd64; sizes above; budget script passes all three.
    • All three smoke checks run locally against the built images, green:
    core smoke: /readyz ok:true, migrations applied
    browser smoke: Browser Host healthy and headless Chromium launched
    cli smoke: pane host accepted a framed ping
    
    • node --test scripts/tests/*.mjs green, including the new container-image-trim.test.mjs and the updated container-image-size-budget.test.mjs; public-docker-distribution and public-compose-config pass unchanged.
    • Image contents verified: per-target production closure only, cross-arch native prebuilds removed by the trim step, entrypoints / non-root user / healthcheck / wrapper links / OpenCode in cli / notices preserved.

    The launcher full-stack check (build → tag → install --profile large → OpenCode pane + browser pane) is in progress; results will be posted in the PR. Happy to address any feedback on #205.

  23. Schultzenator commented on Oct 18, 2026

    @Schultzenator

    Full-stack launcher check is complete: the launcher brought up the whole runtime from locally built per-service images (linux/amd64), all containers healthy, web UI / → 200 and /readyz → ok:true, OpenCode pane answered a framed ping (ok:true) and the Browser Host answered a framed health (Managed Chrome browser sessions are enabled). Full evidence: #205 (comment)

    arm64 builds are still in progress locally — I will follow up with the measured sizes there as well.

  24. achraflamarti commented on Oct 18, 2026

    @achraflamarti

    Hi @oll4com — I have read the #64 terms, including the first-merged-PR rule, the real Linux large-profile validation, and the requirement for personal review/testing. I am not claiming this bounty or promising that those checks have been completed.

    Given the existing implementation PRs, would a separate, smaller paid documentation task be useful? Proposed scope for US$25, subject to your agreement: a contributor-facing validation checklist mapping every #64 acceptance criterion to its command, expected evidence, and local-versus-CI requirement, including the 8-available-CPU full-stack prerequisite. Deliverable: one focused documentation PR, with each command checked against the current repository source. It would not claim that any container build or full-stack test had been run.

    If this is useful, please confirm the scope, price, and what you would require before payment. Otherwise no action is needed; I will not submit a duplicate implementation or treat this as a reservation.

  25. oll4com commented on Oct 18, 2026

    @oll4com
    OwnerAuthor

    @achraflamarti from the posted terms: there are no separate paid tasks, only the posted bounties. This issue's bounty is for the complete solution of the acceptance criteria above, so a separate paid documentation task is not offered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed💎 BountyCash bounty paid after merge; terms in the issue

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions