Run the App build worker
Building an App’s frontend (React, Svelte or a plain source folder) does not
happen inside the API process. A separate build worker polls the queue,
claims one build at a time, runs vite build, and reports the outcome. This page
is how you run it.
Why this page exists
Section titled “Why this page exists”On 2026-08-28 a fleet-wide sweep for orphaned containers found one on the stage
host that had been running for 58 days under the name
stage-app-builder-oneshot. It carried no com.docker.compose.project label,
so nothing owned it and nothing would ever have restarted it. It was one
decision away from being reaped as garbage.
It was not garbage. It was the build worker, behaving exactly as designed.
Two things made that near-miss possible, and this page closes both:
- The name lied. Nothing about
oneshotis true of a queue poller. - Its absence is invisible. There is no alarm on “nobody is polling the build queue”. Remove the worker and App builds simply queue forever; nothing fails, nothing pages, and the first symptom is a user reporting that a build “does nothing”.
Deploy it as a supervised service
Section titled “Deploy it as a supervised service”Run it as a compose service, never as a hand-started docker run. A
hand-started container does not survive a host reboot, carries no project label,
and is indistinguishable from litter to anyone cleaning up.
services: supacloud-app-builder: image: git.blockworx.tech/blockworx/supacloud-agent-unified:node restart: unless-stopped environment: AGENT_TYPE: app_builder SUPACLOUD_API_URL: https://app.example.com RUNNER_TOKEN: ${SUPACLOUD_RUNNER_TOKEN}Name it for what it is. supacloud-app-builder is the canonical name; anything
containing oneshot, job, or temp is wrong and will invite deletion.
The environment contract
Section titled “The environment contract”| Variable | Required | Meaning |
|---|---|---|
AGENT_TYPE |
yes | Must be exactly app_builder. It selects the short-circuit in the runner’s entry point, before the harness-adapter registry is consulted. |
SUPACLOUD_API_URL |
yes | Base URL of the SupaCloud API. Trailing slashes are stripped. |
RUNNER_TOKEN |
yes | The runner credential. SUPACLOUD_RUNNER_TOKEN is accepted as an alias. |
BUILD_POLL_INTERVAL_MS |
no | Milliseconds between polls of an empty queue. Defaults to 5000; a non-numeric or non-positive value falls back to the default rather than failing. |
BUILD_ALLOW_POSTINSTALL |
no | Accepts 1 or true. Permits npm postinstall scripts during the build. Leave it unset. It is off by default because a postinstall script is arbitrary code from a dependency tree. |
Do not set TASK_ID or TASK_PROMPT. Every other runner mode requires
them; this mode is explicitly exempt, because it is not bound to a task. Setting
them does no harm but misrepresents the process.
A missing SUPACLOUD_API_URL or RUNNER_TOKEN makes the worker exit
immediately with a named error rather than polling silently, so
restart: unless-stopped is safe: a misconfigured worker crash-loops visibly
instead of pretending to work.
Run more than one
Section titled “Run more than one”The queue claim is transactional and uses FOR UPDATE SKIP LOCKED, so two
workers never claim the same build. Running a second worker is therefore both
safe and the simplest defence against the silent-stall failure above: with one
worker, its loss stops all builds; with two, the survivor keeps the queue moving.
Check that it is alive
Section titled “Check that it is alive”The worker’s health is not “the container is up” — a poller with a bad token that crash-loops looks up between restarts. Check the queue instead:
- From the fleet: the container exists, carries a compose project label, and
its log shows
Starting unified runner in app_builder modefollowed by poll activity rather than a repeating startup error. - From the product: trigger a build for any App and confirm it leaves the queued state. A build that stays queued while the API is healthy means nothing is claiming it.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Builds stay queued forever, API healthy | No worker is running, or every worker is crash-looping | Check the service is up and read its first log line |
Worker exits immediately, SUPACLOUD_API_URL is required |
Variable unset or empty | Set it; note that a trailing slash is stripped, not rejected |
Worker exits immediately, RUNNER_TOKEN is required |
Neither RUNNER_TOKEN nor SUPACLOUD_RUNNER_TOKEN set |
Set one |
| Worker polls but never claims | Queue is genuinely empty, or the token lacks access | Queue a build and watch the log |
| Container disappeared after a host reboot | It was hand-started, not a compose service | Deploy it as shown above |
Related
Section titled “Related”- Set up a runner securely — the runner credential this worker uses.
- Separate-origin App hosting — where the artefact this worker produces is served from.