Scalix Build
Build container images from a Git repo or an uploaded source tree. Runtime auto-detection, generated Dockerfiles, and per-build Firecracker isolation.
Overview
Scalix Build turns source code into a container image. Point it at a Git repo or upload a tarball; it detects the runtime, builds an OCI image, and pushes it to the Scalix Container Registry — ready to run on Scalix Run or Functions.
Every build runs in its own ephemeral Firecracker microVM with BuildKit, torn down when the build finishes. Nothing is shared between builds.
Base URL: https://api.scalix.world/v1/build
Scope: compute:deploy — the API key must be project-scoped
Quick Start
The fastest path is the CLI, which packages the current directory, builds it, and deploys the result as a service:
scalix-cloud deploy ./my-app1/4 packaging source
2/4 uploading source (3.4 MB) and starting build
build 8f2c1a90-...
3/4 building
4/4 deployingOr drive the API directly:
curl -X POST https://api.scalix.world/v1/build \
-H "Authorization: Bearer $SCALIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_type": "git",
"source_url": "https://github.com/yourorg/api.git",
"source_ref": "main",
"image_tag": "api:1.4.0"
}'{
"build": {
"id": "8f2c1a90-...",
"status": "pending",
"source_type": "git",
"source_ref": "main",
"image_tag": "api:1.4.0",
"timeout_seconds": 600,
"created_at": "2026-08-02T09:14:22Z"
}
}Build from Git
POST /v1/build returns 201 Created.
| Field | Type | Notes |
|---|---|---|
source_type | string | Required. git or tarball |
source_url | string | Repo or archive URL. Must be a public http(s) URL — internal, loopback, and metadata hosts are rejected |
source_ref | string | Branch, tag, or commit. Defaults to main. This is the branch field — git_ref is not recognised and is silently ignored |
auth_token | string | Credential for a private repo (see below) |
dockerfile_path | string | Path to the Dockerfile in the tree. Auto-detected if omitted |
image_tag | string | Tag for the resulting image. Auto-generated if omitted |
build_args | object | Build-time ARGs (see below) |
timeout_seconds | int | Default 600, maximum 3600 |
Private repositories
Two options — pick one.
Install the Scalix GitHub App (recommended). Once installed for the project,
builds mint a short-lived installation token per build automatically. Omit
auth_token entirely.
# List repos the installation can reach
curl https://api.scalix.world/v1/integrations/github/repos \
-H "Authorization: Bearer $SCALIX_API_KEY"Or pass a token — a fine-grained PAT with read-only Contents access on that one repository is enough:
curl -X POST https://api.scalix.world/v1/build \
-H "Authorization: Bearer $SCALIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_type": "git",
"source_url": "https://github.com/yourorg/private-api.git",
"auth_token": "github_pat_..."
}'The token is held in memory for the build only. It is never stored, never written to build logs, and never echoed in error messages.
Build from an Upload
When the source isn't reachable from the internet, upload it instead. The request body is the gzipped tarball — metadata goes in the query string. Multipart is not accepted.
POST /v1/build/upload returns 201 Created.
tar czf - -C ./my-app . | curl -X POST \
"https://api.scalix.world/v1/build/upload?image_tag=api:1.4.0&timeout_seconds=900" \
-H "Authorization: Bearer $SCALIX_API_KEY" \
-H "Content-Type: application/gzip" \
--data-binary @-| Query param | Notes |
|---|---|
image_tag | Tag for the resulting image. Auto-generated if omitted |
dockerfile_path | Path within the uploaded tree. Auto-detected if omitted |
build_args | URL-encoded JSON object, e.g. {"VITE_API_URL":"https://api.example.com"} |
timeout_seconds | Default 600, maximum 3600 |
Maximum upload: 256 MB gzipped. A body that is empty, not a gzipped
tarball, oversized, or has malformed build_args returns 400.
Runtime Auto-Detection
With no Dockerfile in the tree, Build detects the runtime and generates one. Detection runs in this order — first match wins:
| Order | Trigger file | Runtime | Generated image |
|---|---|---|---|
| 1 | Dockerfile | your own | Built as-is; nothing generated |
| 2 | package.json | Node | node:22-alpine multi-stage, port 3000 |
| 3 | requirements.txt / pyproject.toml / setup.py | Python | python:3.12-slim + gunicorn, port 8000 |
| 4 | go.mod | Go | golang:1.22-alpine to alpine:3.19, port 8080 |
| 5 | Cargo.toml | Rust | rust:1.82-bookworm to debian:bookworm-slim, port 8080 |
| 6 | index.html | Static | nginx:1.27-alpine, port 8080 |
| — | none of the above | unknown | Build fails — add a Dockerfile |
Node and Python versions come from the source when declared (engines.node,
.nvmrc, python_requires); the table shows the fallback. index.html is
checked last so a framework that also ships one keeps its real toolchain
detection.
All generated images run as USER 1000, never root.
Static sites
If a build script is detected, the output directory is served by nginx. The
toolchain-declared output dir is probed first, then dist, build, out,
public, _site — first non-empty wins. A build that produces no output
directory fails loudly rather than shipping an empty site. The nginx vhost
listens on 8080 with an SPA history fallback, so client-side routes deep-link.
Build Args and Environment Variables
Project environment variables are merged into the build automatically, and the
generated Dockerfile declares each key as ARG + ENV in the build stage — so
NEXT_PUBLIC_* / VITE_* values exist when the bundler bakes them in.
build_args sent on the request win over project variables.
Build args are not secret. They are stored on the build record and readable back from
GET /v1/build/{id}. Use them for a public API base URL, not a token. Runtime secrets belong in service environment variables.
Tracking a Build
Builds move through these states:
pending → fetching → detecting → building → completed
↘ failed
↘ cancelledTerminal statuses are sticky — a cancelled build can never flip to completed.
Get a build
curl https://api.scalix.world/v1/build/$BUILD_ID \
-H "Authorization: Bearer $SCALIX_API_KEY"The record carries status, detected_runtime, image_ref, image_tag,
build_args, error_message, timeout_seconds, and the started_at /
completed_at / created_at timestamps.
List builds
curl https://api.scalix.world/v1/build \
-H "Authorization: Bearer $SCALIX_API_KEY"Logs
curl https://api.scalix.world/v1/build/$BUILD_ID/logs \
-H "Authorization: Bearer $SCALIX_API_KEY"Returns build_id, status, and logs.
Cancel
curl -X POST https://api.scalix.world/v1/build/$BUILD_ID/cancel \
-H "Authorization: Bearer $SCALIX_API_KEY"Cancellation is real mid-build: the build VM is stopped, and a cancelled build bills nothing — the completion callback that triggers billing only fires on a genuine completion.
The Resulting Image
Images land in the Scalix Container Registry, keyed by project id. The
project_id prefix is mandatory:
registry.scalix.world/<project-id>/<image-tag>Pull it like any OCI image:
docker pull registry.scalix.world/8f2c1a90-.../api:1.4.0Then run it. Scalix Run only accepts images from the Scalix registry, so build and push first:
curl -X POST https://api.scalix.world/v1/services \
-H "Authorization: Bearer $SCALIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "api",
"image": "registry.scalix.world/8f2c1a90-.../api:1.4.0",
"port": 8080,
"min_instances": 1
}'Build on Git Push
Connect a repo once, then every push builds — and optionally deploys.
curl -X POST https://api.scalix.world/v1/integrations \
-H "Authorization: Bearer $SCALIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"repo_url": "https://github.com/yourorg/api",
"branch_filter": "main",
"auto_build": true,
"auto_deploy": true,
"service_id": "svc_..."
}'The response returns the webhook_url and webhook_secret to register with
your provider. With the Scalix GitHub App installed, the App's own webhook is
accepted too — no manual webhook needed.
| Field | Notes |
|---|---|
provider | github, gitlab, or bitbucket |
branch_filter | Only pushes to this branch trigger a build |
auto_build | Build on push |
auto_deploy | Deploy the built image to service_id |
preview_enabled / preview_ttl_hours | Ephemeral preview deploys |
Manage integrations with GET, PUT, and DELETE on /v1/integrations/{id}.
Isolation and Limits
| Isolation | One ephemeral Firecracker microVM per build, destroyed after |
| Build VM size | 2 vCPU / 4 GB RAM |
| Build disk | 8 GB (hard cap — the rootfs image size is all the build gets) |
| Concurrent builds | 4 per machine |
| Upload size | 256 MB gzipped |
| Timeout | 600 s default, 3600 s max |
The builder service itself runs unprivileged with no build tooling installed — untrusted Dockerfiles never execute outside their microVM.
Agents (MCP)
| Tool | Arguments |
|---|---|
scalix_build_create | name, git_url, branch (default main), dockerfile (default Dockerfile) |
scalix_build_status | build_id |
Both require the compute:deploy scope. See MCP.
CLI
scalix-cloud deploy ./my-app # package + build + deploy
scalix-cloud api build list-builds
scalix-cloud api build create-build --body '{"source_type":"git","source_url":"..."}'
scalix-cloud api build get-build $BUILD_ID
scalix-cloud api build get-build-logs $BUILD_ID
scalix-cloud api build cancel-build $BUILD_IDscalix-cloud api build upload-build sends its --body as JSON, so it cannot
carry a tarball. To upload source, use scalix-cloud deploy (which packages and
posts the gzip stream for you) or the raw curl above.
Pricing
Rates verified against the live price book, 2026-08-02.
| Metric | Rate | Billed on |
|---|---|---|
Per build (build_starts) | $0.009 | Successful builds only |
Build minutes (build_minutes) | $0.0054/minute | Actual build duration, rounded up to whole minutes |
vCPU-hour (vm_vcpu_hours) | $0.036 | Build VM allocation × duration |
GiB-hour (vm_memory_gb_hours) | $0.004 | Build VM allocation × duration |
Registry storage (registry_storage_gb) | $0.09/GB/month | Image blobs at rest |
A build charges all four build meters: one build_starts, the rounded-up
minutes, and the VM's vCPU and memory allocation × duration.
Failed and rejected builds are not metered. Cancelled builds bill nothing.
Included builds
The monthly allowance is counted in builds, not minutes.
| Plan | Included builds/month |
|---|---|
| Free | None — see below |
| Starter | 100 |
| Pro | 500 |
| Team | 2,000 |
| Business | 10,000 |
| Enterprise | Unlimited |
Build minutes, vCPU-hours, GiB-hours, and registry storage carry no included allowance on any plan — they bill from the first unit. Registry pushes, pulls, and egress have their own per-plan allowances.
The Free plan has no build allowance. Builds draw against your starting credit balance, and because free accounts have overage disabled, the account becomes read-only once that balance is exhausted — Build and Registry stop accepting writes until you add credit or move to a paid plan.
Console
- Project → Builder — build history, live logs, cancel
- Project → Registry — images, tags, scan results
- Project → Settings → Integrations — connect repos, GitHub App install
Troubleshooting
| Symptom | Cause |
|---|---|
missing scope: compute:deploy | The API key lacks the scope |
API key must be project-scoped for build operations | An org-level key was used — mint a project key |
| Build fails at detection | No Dockerfile and no recognised trigger file — add a Dockerfile |
| Static build fails: "build produced no output directory" | The build script wrote nowhere Build probes — set the output dir in your toolchain config |
400 on upload | Body empty, not gzipped, over 256 MB, or malformed build_args |
Push rejected with 401 | Registry auth — re-check the project id prefix on the image tag |
| Private repo clone fails | GitHub App not installed for the project and no auth_token passed |
| Build times out at 600 s | Raise timeout_seconds (max 3600) |
See Also
- Build API reference — every endpoint, generated from the OpenAPI spec
- Scalix Run — deploying the built image
- Functions — serverless deployment
- Domains — pointing a domain at a service