KAS Build (Cloud Build)
Metro + Hermes bytecode compile service. Accepts a source tarball, runs
metro bundle inside the appunvs/cloud-build-mobile:latest Docker image, and
returns a .jsbundle artifact. Analogous to EAS Build but scoped to JS
bundles only (no native compilation — that lives in keel/packaging/).
Source: keel/cloud/cmd/build-server/
Build image: keel/cloud/packaging/build-cloud-build.sh → appunvs/cloud-build-mobile:latest
Callers invoke via the HTTP API or directly through the Go
cloudbuild.Builder interface (implementations: LocalStub for CI,
DockerBuilder for production).
Self-hosting the build server
Run
cd keel
go run ./cmd/build-server # :8082, ./data/build-artifacts, mem store, stub builder
go run ./cmd/build-server -listen :9090
go run ./cmd/build-server -stub-latency 5s # simulate slow builds
go run ./cmd/build-server -db sqlite:./data/cb.db # persist job state
go run ./cmd/build-server \
-db sqlite:./data/cb.db \
-builder docker:appunvs/cloud-build-mobile:latest # real metro+Hermes
Before using -builder docker:<image>, build the image locally:
bash keel/cloud/packaging/build-cloud-build.sh # ~3–5 min first time
Environment variables
| Env var | Flag | Default | Notes |
|---|---|---|---|
KEEL_BUILD_LISTEN | -listen | :8082 | |
KEEL_BUILD_BLOB_ROOT | -blob-root | ./data/build-artifacts | |
KEEL_BUILD_URL_BASE | -url-base | — | Public URL prefix for artifact downloads |
KEEL_BUILD_MODE | -mode | debug | debug / release / test |
KEEL_BUILD_STUB_LATENCY | -stub-latency | 0 | Duration string, e.g. 5s |
KEEL_BUILD_DB | -db | mem | mem or sqlite:<path> |
KEEL_BUILD_BUILDER | -builder | stub | stub or docker:<image> |
KEEL_BUILD_AUTH_KEYS_FILE | -auth-keys-file | — | JSON file of API keys |
KEEL_BUILD_AUTH_KEYS | -auth-keys | — | Inline: key:project[:label],... |
Build store backends
| DSN | Use case |
|---|---|
mem (default) | Dev / CI. Lost on restart |
sqlite:<path> | Self-host single node. Survives restart |
| (V1 GA) Postgres | HA / cluster |
Auth
All routes require Authorization: Bearer <key> — build is fully
developer-facing; no public surface.
Default: no auth (local dev / smoke). Never ship a public endpoint without keys.
Inline keys:
go run ./cmd/build-server \
-auth-keys "keel_live_alice:proj1:alice,keel_local_dev:*:dev"
File keys (-auth-keys-file keys.json):
{
"keys": [
{"key": "keel_live_a3f...", "project": "myproj", "label": "production CI"},
{"key": "keel_admin_xyz", "project": "*", "label": "admin"}
]
}
Project * = wildcard. CLI reads key from keel.json’s api_key,
KEEL_API_KEY env var, or --api-key flag.
HTTP API reference
| Method | Route | Notes |
|---|---|---|
POST | /v1/{project}/build | Submit build (JSON + tarball base64) |
GET | /v1/{project}/builds/{build_id} | Poll status |
GET | /v1/{project}/builds/{build_id}/artifact | Download artifact bytes |
POST | /v1/{project}/builds/{build_id}/cancel | Cancel queued build |
GET | /v1/{project}/builds | List builds (?limit=N, default 50) |
Submit body
{
"platform": "ios",
"runtime_version": "1.0",
"size_bytes": 12345,
"tarball_base64": "..."
}
Returns 202 Accepted with a build_id. Build state machine:
queued → running → succeeded | failed | cancelled
Poll response
{
"build_id": "bld_...",
"state": "succeeded",
"artifact_hash": "sha256:...",
"artifact_url": "...",
"logs": ["..."],
"started_at": "...",
"finished_at": "..."
}
Smoke test
go run ./cmd/build-server -listen :19292 -blob-root /tmp/cb &
TARBALL_B64=$(echo -n "fake source" | base64)
curl -s -X POST http://localhost:19292/v1/myproj/build \
-H 'Content-Type: application/json' \
-d "{\"platform\":\"ios\",\"runtime_version\":\"1.0\",\"size_bytes\":11,\"tarball_base64\":\"$TARBALL_B64\"}" | jq '.build_id'
# Poll
curl -s http://localhost:19292/v1/myproj/builds/bld_.../ | jq '.state'
Or via CLI:
keel build ios --project myproj --endpoint http://localhost:19292
Status (V1 → V1 GA)
- ✅ HTTP API: submit / status / list / download / cancel
- ✅ In-memory BuildStore (
mem) - ✅ SQLite BuildStore (
sqlite:<path>) - ✅ LocalFSBlobStore
- ✅ StubBuilder (CI / smoke)
- ✅ DockerBuilder (
docker:<image>— real metro+Hermes) - ✅ Async build via goroutine + state machine
- ✅ API key auth
- ✅ E2E test coverage
- ❌ Multipart upload (V2)
- ❌ Postgres BuildStore (V1 GA)
- ❌ S3 / OSS BlobStore (V1 GA)
- ❌ Real queue (V1 = in-process goroutine; V1 GA = Redis Streams / Aliyun MNS)
- ❌ Cancellation propagation to running goroutine (V1 GA)
- ❌ Build log streaming via SSE (V1 GA)
- ❌ iOS native packaging
.ipa(V1.5+, requires macOS runner) - ❌ HA / cluster mode (V2)
Docker build image
appunvs/cloud-build-mobile:latest — Node 22-Alpine base; bakes in metro + Hermes.
# Build the image
bash keel/cloud/packaging/build-cloud-build.sh
# What the DockerBuilder does internally:
docker run --rm \
-v <tmp-source>:/work/src \
-v <tmp-out>:/work/out \
appunvs/cloud-build-mobile:latest
# → /work/out/index.jsbundle
Override npm registry for China-deployed workers:
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
Customers building non-RN targets (e.g. Next.js / Electron / WeChat MP) should layer their own build images on top of the KAS Build pipeline.
SDK versioning
The build image and SDK version are coupled via keel/version.json
(runtime.sdk_version). Bump it when:
- The Native SDK ABI changes
Host shells pin to a specific SDK version range; bundles record which SDK version they were built against so the host can refuse incompatible loads.
See also
pricing.md— managed Build SLA + tiersdeployment.md— production infrakeel/cloud/cmd/build-server/— server sourcekeel/cloud/packaging/build-cloud-build.sh— image build scriptkeel/cloud/internal/cloudbuild/— DockerBuilder + SQLite store