sun Documentation

Introduction

sun is a private cloud control plane for lightweight, isolated virtual machines. It lets you provision secure, memory-safe environments in milliseconds — from a terminal, a mobile phone, or a no-code canvas — without touching a hypervisor or writing a Dockerfile.

You bring the hardware. sun manages the fleet.

What sun is

sun is the layer between your servers and the workloads you want to run on them. It handles:

Core concepts

Workspace VM

A workspace VM is an on-demand isolated microVM for interactive use — development, agent execution, running a one-off job, or hosting a long-lived process. It has a shell, a unique VM ID (vm-XXXX), and an optional build step on launch.

Workspace VMs can be persistent (survive stop/start) or ephemeral (destroyed when the session ends).

Deployment

A deployment is the long-running counterpart to a workspace VM. It is the unit of traffic routing — each deployment gets a subdomain, TLS certificate, and sits behind the reverse proxy. Deployments have a status lifecycle: queued → booting → running.

Build

A build compiles source code into a deployable artifact. Builds run inside isolated ephemeral microVMs so they never share state. Builds go through: queued → building → succeeded | failed.

Project

A project groups deployments, storage volumes, and database instances into a shared internal network. Resources inside the same project can reach each other by service name. The no-code canvas (GET/PUT /projects/:id/canvas) provides a visual view of the connections.

Infra Agent

An infra agent is a monitoring daemon running on each physical host. The launcher queries agents to schedule new VMs on nodes with available capacity. The sun capacity CLI command shows live per-agent CPU, memory, and disk utilisation.

Storage Volume

A storage volume is persistent block storage that can be attached to a workspace VM or deployment. Volumes outlive the VMs they are attached to.

How it fits together

                   ┌──────────────────┐
                   │   sun launcher   │   (this API)
                   └────────┬─────────┘
          ┌─────────────────┼──────────────────┐
          ▼                 ▼                  ▼
   ┌─────────────┐  ┌──────────────┐  ┌──────────────┐
   │  Workspace  │  │  Deployment  │  │    Build     │
   │  VM (shell) │  │  (traffic)   │  │  (pipeline)  │
   └──────┬──────┘  └──────┬───────┘  └──────┬───────┘
          │                │                 │
          └────────────────┴─────────────────┘
                           │
               isolated microVM
               on your infrastructure

The launcher talks to infra agents on each physical host to decide where to schedule a new VM. Once booted, workspace VMs are reachable via SSH through the bastion, and deployments are reachable via the reverse proxy on their assigned subdomain.

Quick start

# Install the CLI
curl -sSL https://sun.run/install | sh

# Point it at your launcher
sun config set endpoint https://launcher.example.com
sun config set token <your-api-token>

# Launch a workspace VM
sun deploy --image ubuntu-22 --region eu-1

# See what's running
sun deployments

# Check fleet capacity
sun capacity

API Reference

All endpoints are served by the sun launcher. Authenticate with a bearer token in the Authorization header unless noted otherwise.

Authorization: Bearer <token>

Tokens are minted via POST /auth/token/mint after login. Base URL is your launcher instance, e.g. https://launcher.example.com.

User tokens are tenant-scoped: they can only access resources in projects under accounts where the user is a member (account_users). The launcher system token remains operator-scoped.


Quickstart

Pick the first step based on your source type.

Short rule:

Source typeFirst callNext callNotes
GitHub repoPOST /deployments/githubNoneCreates the deployment and queues the build automatically.
Repo tar / zipPOST /buildsPOST /builds/:id/sourceThe build must exist first; the upload attaches to that build.
Existing deployment rebuildPOST /builds with deployment_idPOST /builds/:id/sourceUse this when you already have a deployment and want to rebuild it.

Rule of thumb: deployment = runtime identity. build = compilation job. For tar/zip uploads, create the build first.

Runtime note for Node/Express apps: the build worker now packages server-side bundles with their dependencies when it detects a server-style package.json. At runtime, the launcher exports PORT from the deployment runtime port, so app code should read process.env.PORT instead of hardcoding a listener port like 5001.

  1. Save your token and project in environment variables.
  2. Create deployments/builds using only server-generated IDs.
export LAUNCHER_BASE_URL="https://launcher.haifa.my"
export LAUNCHER_API_KEY="<u1_token>"
export LAUNCHER_PROJECT_ID="<proj-xxxx>"

Verify Access

curl -sS "$LAUNCHER_BASE_URL/projects" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY"

Create Deployment (Server Generates dep-xxxx)

DEPLOYMENT_ID=$(
  curl -sS -X POST "$LAUNCHER_BASE_URL/deployments" \
    -H "Authorization: Bearer $LAUNCHER_API_KEY" \
    -H "content-type: application/json" \
    -d "{
      \"project_id\":\"$LAUNCHER_PROJECT_ID\",
      \"region\":\"eu-west-1\",
      \"repo_id\":\"https://github.com/<owner>/<repo>\",
      \"persistence_mode\":\"ephemeral\"
    }" | jq -r '.id'
)
echo "$DEPLOYMENT_ID"

This endpoint is for deployments that already have an internal repo reference or deployment context. If you are starting from a GitHub URL, use POST /deployments/github instead.

Create Deployment From GitHub URL

curl -sS -X POST "$LAUNCHER_BASE_URL/deployments/github" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d "{
    \"project_id\":\"$LAUNCHER_PROJECT_ID\",
    \"repo_url\":\"https://github.com/<owner>/<repo>\",
    \"region\":\"eu-west-1\",
    \"git_ref\":\"main\",
    \"persistence_mode\":\"ephemeral\",
    \"env_mode\":\"merge\",
    \"env\":{
      \"VITE_TRADLY_PUBLISHABLE_KEY\":\"<publishable-key>\",
      \"VITE_TRADLY_BASE_URL\":\"https://api.tradly.app\"
    }
  }"

Use this when the source of truth is a GitHub repository and you want launcher to create the deployment and queue the build automatically.

Example: GitHub deploy with env

curl -sS -X POST "$LAUNCHER_BASE_URL/deployments/github" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d "{
    \"project_id\":\"$LAUNCHER_PROJECT_ID\",
    \"repo_url\":\"https://github.com/<owner>/<repo>\",
    \"region\":\"eu-west-1\",
    \"git_ref\":\"main\",
    \"persistence_mode\":\"ephemeral\",
    \"env_mode\":\"merge\",
    \"env\":{
      \"VITE_TRADLY_PUBLISHABLE_KEY\":\"<publishable-key>\",
      \"VITE_TRADLY_BASE_URL\":\"https://api.tradly.app\"
    }
  }"

This is the recommended shape when you deploy directly from GitHub and need build-time env baked into the frontend bundle.

Deployment Runtime Policy

Runtime policy controls whether a deployment stays hot or cold-starts on request.

Modes:

Set always-on:

curl -sS -X PUT "$LAUNCHER_BASE_URL/deployments/$DEPLOYMENT_ID/policy" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "mode": "always_on",
    "auto_start_on_request": true
  }'

Hibernate after seven days without traffic:

curl -sS -X PUT "$LAUNCHER_BASE_URL/deployments/$DEPLOYMENT_ID/policy" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "mode": "on_inactive",
    "auto_start_on_request": true
  }'

Set cold-start mode:

curl -sS -X PUT "$LAUNCHER_BASE_URL/deployments/$DEPLOYMENT_ID/policy" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "mode": "on_request",
    "auto_start_on_request": true
  }'

Trigger Build For Existing Deployment

BUILD_ID=$(
  curl -sS -X POST "$LAUNCHER_BASE_URL/builds" \
    -H "Authorization: Bearer $LAUNCHER_API_KEY" \
    -H "content-type: application/json" \
    -d "{
      \"project_id\":\"$LAUNCHER_PROJECT_ID\",
      \"deployment_id\":\"$DEPLOYMENT_ID\",
      \"region\":\"eu-west-1\",
      \"env_mode\":\"merge\",
      \"env\":{
        \"NPM_TOKEN\":\"<github-packages-token>\",
        \"WHATSAPP_CHANNEL_LINK\":\"https://whatsapp.com/channel/...\"
      }
    }" | jq -r '.id'
)
echo "$BUILD_ID"

Use this when you already have a deployment record and want to rebuild it without creating a new deployment first.

Upload Source Bundle (.tar.gz, .tgz, or .zip)

For tar/zip flows: create the build first, then upload the source to /builds/:id/source.

curl -sS -X POST "$LAUNCHER_BASE_URL/builds/$BUILD_ID/source" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/zip" \
  -H "x-source-name: local:app.zip" \
  --data-binary @app.zip

Use content-type: application/gzip when uploading .tar.gz or .tgz.

Build-First Flow (for tar/zip uploads)

If deployment_id is omitted, launcher can create one server-side for tenant-scoped flow.

curl -sS -X POST "$LAUNCHER_BASE_URL/builds" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/json" \
  -d "{
    \"project_id\":\"$LAUNCHER_PROJECT_ID\",
    \"region\":\"eu-west-1\",
    \"repo_id\":\"local-upload\",
    \"persistence_mode\":\"ephemeral\",
    \"env_mode\":\"merge\",
    \"env\":{
      \"NPM_TOKEN\":\"<github-packages-token>\"
    }
  }"

Use this flow when you are uploading a repo archive instead of deploying directly from GitHub.

Example: tar/zip build with env

BUILD_ID=$(
  curl -sS -X POST "$LAUNCHER_BASE_URL/builds" \
    -H "Authorization: Bearer $LAUNCHER_API_KEY" \
    -H "content-type: application/json" \
    -d "{
      \"project_id\":\"$LAUNCHER_PROJECT_ID\",
      \"region\":\"eu-west-1\",
      \"repo_id\":\"local-upload\",
      \"persistence_mode\":\"ephemeral\",
      \"env_mode\":\"merge\",
      \"env\":{
        \"VITE_TRADLY_PUBLISHABLE_KEY\":\"<publishable-key>\",
        \"VITE_TRADLY_BASE_URL\":\"https://api.tradly.app\"
      }
    }" | jq -r '.id'
)

curl -sS -X POST "$LAUNCHER_BASE_URL/builds/$BUILD_ID/source" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY" \
  -H "content-type: application/gzip" \
  -H "x-source-name: app.tgz" \
  --data-binary @app.tgz

This is the recommended shape when clients upload a source archive instead of using GitHub directly.

Check Build Status / Logs

curl -sS "$LAUNCHER_BASE_URL/builds/$BUILD_ID" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY"

curl -sS "$LAUNCHER_BASE_URL/builds/$BUILD_ID/logs?limit=200" \
  -H "Authorization: Bearer $LAUNCHER_API_KEY"

Rules


Authentication

POST /auth/login

Exchange credentials for a session.

Request body

FieldTypeDescription
usernamestringAccount username
passwordstringAccount password

GET /auth/me

Returns the currently authenticated user.

POST /auth/token/mint

Mint a bearer token for API access from your authenticated session.

POST /auth/logout

End the current session.


Workspace VMs

Workspace VMs are on-demand isolated microVMs for interactive development, agent execution, and ephemeral tasks. Each workspace gets its own isolated environment with a shell, optional build pipeline, and an internal network address.

POST /workspaces/empty

Also available as POST /workspace-vm/create

Create a new workspace VM. The VM boots and is assigned a deployment ID and VM ID.

Request body

FieldTypeRequiredDescription
regionstringnoTarget region for the VM
project_idstringnoAssign to a project group for internal networking
persistence_modestringnopersistent or ephemeral
runtime_portnumbernoPort the runtime listens on inside the VM
runtime_kindstringnoRuntime identifier (e.g. shell, node, python)
bundle_urlstringnoPre-built artifact URL to load on boot
vcpu_countnumbernoNumber of vCPUs (default: 1)
mem_size_mibnumbernoMemory in MiB (default: 512)
idle_timeout_secsnumbernoAuto-stop after N seconds of inactivity
env_profilestringnoNamed environment profile to apply
profile_versionstringnoPin a specific profile version
bootstrap_script_keystringnoKey of a bootstrap script to run on first boot
env_modestringnoEnv merge mode: merge (default) or replace
envobjectnoKey/value env map to persist and apply on runtime start

Response

{
  "deployment_id": "dep-3f9a...",
  "region": "eu-1",
  "status": "booting",
  "subdomain": "dep-3f9a",
  "fqdn": "dep-3f9a.sun.run",
  "runtime_port": 8080,
  "container_id": null,
  "vm_id": "vm-08a1...",
  "runtime_kind": "shell",
  "runtime_vendor": null,
  "persistence_mode": "ephemeral"
}

GET /workspace-vm/list

List all workspace VMs.

Response

{
  "workspaces": [
    {
      "deployment_id": "dep-3f9a...",
      "vm_id": "vm-08a1...",
      "status": "running",
      "region": "eu-1",
      "fqdn": "dep-3f9a.sun.run",
      "preview_url": "https://dep-3f9a.sun.run",
      "runtime_port": 8080,
      "created_at": "2025-03-28T02:00:00Z"
    }
  ]
}

GET /workspace-vm/:id/status

Get the current status of a workspace VM.

Path parameter: id — the deployment_id

POST /workspace-vm/:id/start

Start (or restart) a stopped workspace VM.

POST /workspace-vm/:id/stop

Stop a running workspace VM.

Query parameter: force=true — forcefully terminate without graceful shutdown.

GET /workspace-vm/:id/preview

Returns a preview URL for the workspace VM's exposed port.

POST /workspace-vm/:id/exec

Run a command inside a running workspace VM.

Request body

FieldTypeRequiredDescription
commandstringyesShell command to execute
cwdstringnoWorking directory
timeout_msnumbernoMax execution time in milliseconds

GET /workspace-vm/:id/shell

WebSocket endpoint — opens an interactive shell session inside the VM. Used by Sun Shell (mobile app) and the web terminal.

ws://launcher.example.com/workspace-vm/:id/shell?token=<api-token>

POST /workspace-vm/:id/build

Trigger a build inside the workspace VM. Initiates source packaging and hands off to the build pipeline.


Deployments

Deployments are the core unit of the sun runtime — a deployed app, service, or workload running inside a microVM.

POST /deployments

Create a new deployment.

Request body

FieldTypeDescription
regionstringTarget region
repo_idstringGitHub repo ID to deploy
base_domainstringBase domain for generated subdomain (example: client-domain.com). Defaults to launcher root domain. In tenant mode, must already be verified and assigned to your project
persistence_modestringpersistent or ephemeral
env_modestringEnv merge mode: merge (default) or replace
envobjectKey/value env map to persist and apply on runtime start

Response

{
  "id": "dep-abc123",
  "region": "eu-1",
  "status": "queued",
  "repo_id": "gh-repo-id",
  "subdomain": "dep-abc123",
  "fqdn": "dep-abc123.sun.run",
  "runtime_port": 3000,
  "container_id": null,
  "persistence_mode": "persistent"
}

GET /deployments

List all deployments.

GET /deployments/:id

Get a single deployment by ID.

DELETE /deployments/:id

Delete a deployment and stop its VM.

POST /deployments/github

Create a deployment from a GitHub repository, triggering a build automatically.

Request body

FieldTypeDescription
repo_urlstringFull GitHub repo URL
repostringShorthand owner/repo
repo_idstringInternal repo ID
regionstringTarget region
base_domainstringBase domain for generated subdomain (example: client-domain.com). Defaults to launcher root domain. In tenant mode, must already be verified and assigned to your project
git_refstringBranch, tag, or SHA
subdirstringSubdirectory to deploy
persistence_modestringpersistent or ephemeral
auto_syncbooleanAuto-redeploy on push
env_modestringEnv merge mode: merge (default) or replace
envobjectKey/value env map to persist and apply when deployment is promoted and runtime starts

Response

{
  "deployment_id": "dep-abc123",
  "build_id": "bld-xyz789",
  "region": "eu-1"
}

Builds

The build pipeline compiles source code into a deployable artifact. Builds run inside ephemeral microVMs.

POST /builds

Enqueue a new build.

Request body

FieldTypeDescription
deployment_idstringLink to an existing deployment
regionstringBuild region
repo_idstringSource repository
base_domainstringBase domain for generated subdomain when build creates a new deployment (system-scoped flow)
persistence_modestringpersistent or ephemeral
env_modestringEnv merge mode: merge (default) or replace
envobjectKey/value env map to persist and apply to the deployment/build runtime

Response

{
  "id": "bld-xyz789",
  "deployment_id": "dep-abc123",
  "status": "queued"
}

GET /builds

List builds, optionally filtered.

Query parameters

ParamDescription
deployment_idFilter by deployment
regionFilter by region
statusqueued | building | succeeded | failed

GET /builds/:id/logs

Fetch build logs.

Query parameter: limit — max number of lines to return.

Response

{
  "build_id": "bld-xyz789",
  "lines": [
    { "message": "Installing dependencies...", "ts": "2025-03-28T02:01:00Z", "level": "info" }
  ]
}

POST /builds/:id/retry

Retry a failed build.

GET /builds/:id/artifacts

Get the build output artifacts (bundle URL, static URL).

Response

{
  "build_id": "bld-xyz789",
  "deployment_id": "dep-abc123",
  "status": "succeeded",
  "bundle_url": "https://...",
  "static_url": "https://...",
  "failure_class": null,
  "failure_message": null
}

POST /builds/:id/source

Upload source code for a build as .tar.gz, .tgz, or .zip. The launcher normalizes the source bundle into its internal tarball format before dispatching the build. Common GitHub download zips and macOS Finder zips are supported.

Headers

HeaderDescription
content-typeapplication/zip for .zip; application/gzip for .tar.gz or .tgz
x-source-nameOptional label for logs/UI, e.g. local:app.zip

Domains & SSL

POST /deployments/:id/domains

Attach a custom domain to a deployment.

Request body

FieldTypeDescription
domainstringThe custom domain (e.g. app.example.com)
methodstringVerification method: cname or txt
domain_rolestringattached (default) or base_domain for tenant-owned base domains
productionbooleanOptional. Defaults to true for production deployments and false for workspace/preview deployments.

Rule: verified domains attached to production deployments are treated as production automatically. Workspace and preview deployments stay non-production unless you explicitly override it.

Response

{
  "id": "dom-...",
  "deployment_id": "dep-abc123",
  "domain": "app.example.com",
  "domain_role": "attached",
  "is_production": true,
  "status": "pending_verification",
  "verification_method": "cname",
  "verification_record_name": "_sun-verify.app.example.com",
  "verification_record_value": "sun-abc123xyz",
  "cert_status": "pending",
  "cert_issued_at": null
}

GET /deployments/:id/domains

List domains attached to a deployment.

POST /domains/:id/verify

Trigger domain verification after the DNS record has been set. Sun checks the record and issues a TLS certificate automatically on success.


Infra Agents

GET /infra/agents

Returns the health and resource usage of all registered infra agents. This is what sun capacity calls.

Response

{
  "agents": [
    {
      "name": "node-eu-1",
      "endpoint": "https://agent.example.internal",
      "healthy": true,
      "capacity": {
        "total_vcpu": 8,
        "total_mem_mib": 16384,
        "cpu_percent": 12.4,
        "memory_percent": 38.1,
        "disk_percent": 22.7
      }
    }
  ]
}

Monitoring

GET /metrics

Current runtime metrics snapshot.

GET /metrics/timeseries

Time-series metrics for charting.

GET /logs

Recent log lines across all deployments.

GET /runtime/slo

Runtime SLO (service level objective) report — uptime, latency, error rates.


Health

GET /healthz

Returns 200 OK when the launcher is up. No authentication required.


Projects & Accounts

Projects group deployments, storage volumes, and database instances into a shared internal network. Accounts are the top-level billing and user boundary.

Tenant access model: Account -> Project -> Resources. Resources without project_id are hidden from tenant users and are backfilled to a default project on startup when possible.

GET /accounts

List all accounts.

POST /accounts

Create or update an account.

Request body

FieldTypeDescription
idstringOptional — omit to create new
namestringDisplay name

GET /accounts/:id/users

List users in an account.

POST /accounts/:id/users

Add a user to an account with a role.

Request body

FieldTypeDescription
user_idstringUser to add
rolestringRole to assign

GET /projects

List projects. Optionally filter by account.

Query parameter: account_id

POST /projects

Create or update a project.

Request body

FieldTypeDescription
idstringOptional — omit to create new
account_idstringOwning account
namestringProject name

POST /projects/:id/assign

Assign existing resources (deployments, storage, databases) to a project.

Request body

FieldTypeDescription
deployment_idsstring[]Deployments to add
storage_volume_idsstring[]Storage volumes to add
db_instance_idsstring[]Database instances to add

GET /projects/:id/canvas

Fetch the visual no-code canvas layout for a project — nodes and edges representing containers and their connections.

Response

{
  "nodes": [{ "id": "dep-abc123", "type": "deployment", "x": 120, "y": 80 }],
  "edges": [{ "from": "dep-abc123", "to": "dep-xyz789", "label": ":5432" }]
}

PUT /projects/:id/canvas

Save the canvas layout.


Storage Volumes

Persistent block storage that can be attached to workspace VMs.

GET /projects/:id/storage

List storage volumes in a project.

POST /projects/:id/storage

Create a storage volume.

Request body

FieldTypeDescription
idstringOptional — omit to create new
size_gibnumberSize in GiB

Response

{
  "id": "vol-...",
  "project_id": "proj-...",
  "deployment_id": null,
  "size_gib": 20,
  "status": "available",
  "created_at": "2025-03-28T02:00:00Z"
}

POST /projects/:id/storage/:volume_id/attach

Attach a volume to a deployment (workspace VM).

Request body

FieldTypeDescription
deployment_idstringTarget deployment to attach to

Response

{
  "volume_id": "vol-...",
  "deployment_id": "dep-...",
  "mount_path": "/mnt/data"
}

POST /projects/:id/storage/:volume_id/detach

Detach a volume from its current deployment.


SSH Sessions

SSH sessions provide certificate-based access to workspace VMs via the bastion host.

POST /ssh/session

Create an SSH session for a workspace VM. Returns a short-lived signed certificate and connection details.

Request body

FieldTypeDescription
deployment_idstringTarget workspace VM
client_idstringOptional client identifier
principalstringSSH principal (user)
public_keystringYour SSH public key to sign
ttl_secsnumberCertificate TTL in seconds

Response

{
  "deployment_id": "dep-...",
  "vm_id": "vm-...",
  "bastion_host": "bastion.sun.run",
  "bastion_port": 22,
  "bastion_user": "sun",
  "principal": "dev",
  "client_id": "cli-...",
  "ttl_secs": 3600,
  "expires_at": "2025-03-28T03:00:00Z",
  "ssh_cert": "ssh-rsa-cert-v01@openssh.com ...",
  "ssh_public_key": "ssh-ed25519 ...",
  "ssh_private_key": null,
  "ssh_command": "ssh -i cert.pub -p 22 sun@bastion.sun.run"
}