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.shappunvs/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 varFlagDefaultNotes
KEEL_BUILD_LISTEN-listen:8082
KEEL_BUILD_BLOB_ROOT-blob-root./data/build-artifacts
KEEL_BUILD_URL_BASE-url-basePublic URL prefix for artifact downloads
KEEL_BUILD_MODE-modedebugdebug / release / test
KEEL_BUILD_STUB_LATENCY-stub-latency0Duration string, e.g. 5s
KEEL_BUILD_DB-dbmemmem or sqlite:<path>
KEEL_BUILD_BUILDER-builderstubstub or docker:<image>
KEEL_BUILD_AUTH_KEYS_FILE-auth-keys-fileJSON file of API keys
KEEL_BUILD_AUTH_KEYS-auth-keysInline: key:project[:label],...

Build store backends

DSNUse case
mem (default)Dev / CI. Lost on restart
sqlite:<path>Self-host single node. Survives restart
(V1 GA) PostgresHA / 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

MethodRouteNotes
POST/v1/{project}/buildSubmit build (JSON + tarball base64)
GET/v1/{project}/builds/{build_id}Poll status
GET/v1/{project}/builds/{build_id}/artifactDownload artifact bytes
POST/v1/{project}/builds/{build_id}/cancelCancel queued build
GET/v1/{project}/buildsList 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 + tiers
  • deployment.md — production infra
  • keel/cloud/cmd/build-server/ — server source
  • keel/cloud/packaging/build-cloud-build.sh — image build script
  • keel/cloud/internal/cloudbuild/ — DockerBuilder + SQLite store