ScalixScalix Docs
Sign up

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:

bash
scalix-cloud deploy ./my-app
text
1/4 packaging source
2/4 uploading source (3.4 MB) and starting build
    build 8f2c1a90-...
3/4 building
4/4 deploying

Or drive the API directly:

bash
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"
  }'
json
{
  "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.

FieldTypeNotes
source_typestringRequired. git or tarball
source_urlstringRepo or archive URL. Must be a public http(s) URL — internal, loopback, and metadata hosts are rejected
source_refstringBranch, tag, or commit. Defaults to main. This is the branch field — git_ref is not recognised and is silently ignored
auth_tokenstringCredential for a private repo (see below)
dockerfile_pathstringPath to the Dockerfile in the tree. Auto-detected if omitted
image_tagstringTag for the resulting image. Auto-generated if omitted
build_argsobjectBuild-time ARGs (see below)
timeout_secondsintDefault 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.

bash
# 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:

bash
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.

bash
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 paramNotes
image_tagTag for the resulting image. Auto-generated if omitted
dockerfile_pathPath within the uploaded tree. Auto-detected if omitted
build_argsURL-encoded JSON object, e.g. {"VITE_API_URL":"https://api.example.com"}
timeout_secondsDefault 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:

OrderTrigger fileRuntimeGenerated image
1Dockerfileyour ownBuilt as-is; nothing generated
2package.jsonNodenode:22-alpine multi-stage, port 3000
3requirements.txt / pyproject.toml / setup.pyPythonpython:3.12-slim + gunicorn, port 8000
4go.modGogolang:1.22-alpine to alpine:3.19, port 8080
5Cargo.tomlRustrust:1.82-bookworm to debian:bookworm-slim, port 8080
6index.htmlStaticnginx:1.27-alpine, port 8080
none of the aboveunknownBuild 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:

text
pending → fetching → detecting → building → completed
                                          ↘ failed
                                          ↘ cancelled

Terminal statuses are sticky — a cancelled build can never flip to completed.

Get a build

bash
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

bash
curl https://api.scalix.world/v1/build \
  -H "Authorization: Bearer $SCALIX_API_KEY"

Logs

bash
curl https://api.scalix.world/v1/build/$BUILD_ID/logs \
  -H "Authorization: Bearer $SCALIX_API_KEY"

Returns build_id, status, and logs.

Cancel

bash
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:

text
registry.scalix.world/<project-id>/<image-tag>

Pull it like any OCI image:

bash
docker pull registry.scalix.world/8f2c1a90-.../api:1.4.0

Then run it. Scalix Run only accepts images from the Scalix registry, so build and push first:

bash
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.

bash
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.

FieldNotes
providergithub, gitlab, or bitbucket
branch_filterOnly pushes to this branch trigger a build
auto_buildBuild on push
auto_deployDeploy the built image to service_id
preview_enabled / preview_ttl_hoursEphemeral preview deploys

Manage integrations with GET, PUT, and DELETE on /v1/integrations/{id}.

Isolation and Limits

IsolationOne ephemeral Firecracker microVM per build, destroyed after
Build VM size2 vCPU / 4 GB RAM
Build disk8 GB (hard cap — the rootfs image size is all the build gets)
Concurrent builds4 per machine
Upload size256 MB gzipped
Timeout600 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)

ToolArguments
scalix_build_createname, git_url, branch (default main), dockerfile (default Dockerfile)
scalix_build_statusbuild_id

Both require the compute:deploy scope. See MCP.

CLI

bash
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_ID

scalix-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.

MetricRateBilled on
Per build (build_starts)$0.009Successful builds only
Build minutes (build_minutes)$0.0054/minuteActual build duration, rounded up to whole minutes
vCPU-hour (vm_vcpu_hours)$0.036Build VM allocation × duration
GiB-hour (vm_memory_gb_hours)$0.004Build VM allocation × duration
Registry storage (registry_storage_gb)$0.09/GB/monthImage 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.

PlanIncluded builds/month
FreeNone — see below
Starter100
Pro500
Team2,000
Business10,000
EnterpriseUnlimited

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

SymptomCause
missing scope: compute:deployThe API key lacks the scope
API key must be project-scoped for build operationsAn org-level key was used — mint a project key
Build fails at detectionNo 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 uploadBody empty, not gzipped, over 256 MB, or malformed build_args
Push rejected with 401Registry auth — re-check the project id prefix on the image tag
Private repo clone failsGitHub App not installed for the project and no auth_token passed
Build times out at 600 sRaise timeout_seconds (max 3600)

See Also