Pebbleface Runner is a small HTTP service that accepts a Pebble watchface zip bundle, runs pebble build, and returns the resulting .pbw file.
docker build -t pebbleface-runner .
docker run --rm -p 8787:8787 -e PORT=8787 pebbleface-runnerApple Silicon note (pebble-tool pulls arm64 deps that can fail to build):
docker buildx build --platform linux/amd64 -t pebbleface-runner .Render Native Node builds cannot run apt-get, so this service must be deployed as Docker.
- Set Environment to Docker in Render.
- Clear Build Command / Start Command (Dockerfile handles both).
- Deploy.
Build a watchface:
curl -F "bundle=@./watchface.zip" http://localhost:8787/build -o out.pbwHealth check:
curl http://localhost:8787/healthzIf RUNNER_TOKEN is configured on the server, include X-Runner-Token:
curl -F "bundle=@./watchface.zip" -H "X-Runner-Token: <token>" http://localhost:8787/build -o out.pbw200 OK- Body:
ok
Accepted content types:
multipart/form-data(recommended)application/json(bundle URL)
Multipart fields:
bundle(required, zip file)target(optional)timeoutSec(optional)maxZipBytes(optional)maxUnzipBytes(optional)
JSON body:
{
"bundleUrl": "https://example.com/bundle.zip",
"timeoutSec": 120,
"maxZipBytes": 26214400,
"maxUnzipBytes": 104857600
}Success response:
200 OKContent-Type: application/octet-streamContent-Disposition: attachment; filename="watchface.pbw"- Headers:
X-Job-Id,X-Build-Log-Base64 - Body:
.pbwbinary
Error response (JSON):
{ "ok": false, "error": "...", "detail": "..." }Status codes:
400invalid request or zip413too large429concurrency/queue limit (includesRetry-After)500build failed or pbw not found504timeout
Environment variables:
PORT(default: 8787)MAX_CONCURRENCY(default: 1)MAX_QUEUE(default: 10)DEFAULT_AVG_BUILD_SEC(default: 60)RUNNER_TOKEN(optional, requiresX-Runner-Token)BUILD_TIMEOUT_SEC(default: 120)MAX_ZIP_BYTES(default: 26214400)MAX_UNZIP_BYTES(default: 104857600)
- The service only runs
pebble buildand ignores any scripts in the bundle. - Zip-slip protection and unzip size limits are enforced.
- Excess requests are queued up to
MAX_QUEUE, then return 429 with a dynamicRetry-After. - To pin a Pebble CLI version, update the Dockerfile to install a specific
pebble-toolversion.