Zaroz
Workload guides

Deploy a Workload from a public image

This guide takes you from nothing to a running Workload using only the API. It runs nginx from Docker Hub, but any public image works the same way.

The Workload was previously called a Container; the guide file names keep the old word.

Before you start

You need an account with an organization, a wallet balance or another way to pay, and a token. The examples read two environment variables:

TerminalCode
export ZAROZ_API="https://kernel.zaroz.cloud/api/v2" export ZAROZ_TOKEN="<your session token or personal access token>"

Every request sends Authorization: Bearer $ZAROZ_TOKEN. The TypeScript examples need Node 20 or later (run with npx tsx file.ts); the Python examples need pip install requests. All of them use this small helper, shown once:

TypeScript

TypeScriptCode
const API = process.env.ZAROZ_API ?? "https://kernel.zaroz.cloud/api/v2"; const TOKEN = process.env.ZAROZ_TOKEN!; export async function api(method: string, path: string, body?: unknown) { const res = await fetch(API + path, { method, headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), }); if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${await res.text()}`); return res.status === 204 ? null : res.json(); }

Python

Code
import os, requests API = os.environ.get("ZAROZ_API", "https://kernel.zaroz.cloud/api/v2") S = requests.Session() S.headers["Authorization"] = "Bearer " + os.environ["ZAROZ_TOKEN"] def api(method, path, body=None, **params): r = S.request(method, API + path, json=body, params=params or None) r.raise_for_status() return None if r.status_code == 204 else r.json()

1. Find the product, a plan and a region

List the catalog and pick the Workload product by its name. Then ask for its plans in a region, and for the regions you can buy in.

TerminalCode
curl -s "$ZAROZ_API/catalog/products?page_size=100" -H "Authorization: Bearer $ZAROZ_TOKEN" | jq '.data[] | {id, name, slug}' curl -s "$ZAROZ_API/catalog/regions?product_id=$PRODUCT_ID" -H "Authorization: Bearer $ZAROZ_TOKEN" | jq '.data[] | {id, name, available}' curl -s "$ZAROZ_API/catalog/products/$PRODUCT_ID/plans?region_id=$REGION_ID" -H "Authorization: Bearer $ZAROZ_TOKEN" | jq '.data[] | {id, name, price, available}'
TypeScriptCode
import { api } from "./api"; const products = await api("GET", "/catalog/products?page_size=100"); const product = products.data.find((p: any) => /workload/i.test(p.name)); const regions = await api("GET", `/catalog/regions?product_id=${product.id}`); const region = regions.data.find((r: any) => r.available); const plans = await api("GET", `/catalog/products/${product.id}/plans?region_id=${region.id}`); const plan = plans.data.find((p: any) => p.available);
Code
products = api("GET", "/catalog/products", page_size=100) product = next(p for p in products["data"] if "workload" in p["name"].lower()) regions = api("GET", "/catalog/regions", product_id=product["id"]) region = next(r for r in regions["data"] if r["available"]) plans = api("GET", f"/catalog/products/{product['id']}/plans", region_id=region["id"]) plan = next(p for p in plans["data"] if p["available"])

plan.price is the monthly price; the extra-unit rate is that divided by 720 (see Billing).

2. Quote the purchase

A quote prices the purchase without buying anything. Send the checkout body without name and slug.

TerminalCode
curl -s -X POST "$ZAROZ_API/checkout/quote" \ -H "Authorization: Bearer $ZAROZ_TOKEN" -H "Content-Type: application/json" \ -d "{\"product_id\":\"$PRODUCT_ID\",\"plan_id\":$PLAN_ID,\"region_id\":$REGION_ID}"
TypeScriptCode
const quote = await api("POST", "/checkout/quote", { product_id: product.id, plan_id: plan.id, region_id: region.id, }); console.log(quote.total_cents, quote.currency, quote.cadence_label);
Code
quote = api("POST", "/checkout/quote", { "product_id": product["id"], "plan_id": plan["id"], "region_id": region["id"], }) print(quote["total_cents"], quote["currency"], quote["cadence_label"])

3. Buy it

Checkout creates a pending subscription and the invoice that establishes it. name is yours to choose. slug is the URL segment that addresses the service, unique inside its project; it also becomes the Workload's private name.

TerminalCode
curl -s -X POST "$ZAROZ_API/checkout" \ -H "Authorization: Bearer $ZAROZ_TOKEN" -H "Content-Type: application/json" \ -d "{\"product_id\":\"$PRODUCT_ID\",\"plan_id\":$PLAN_ID,\"region_id\":$REGION_ID,\"name\":\"Web\",\"slug\":\"web\"}"
TypeScriptCode
const order = await api("POST", "/checkout", { product_id: product.id, plan_id: plan.id, region_id: region.id, name: "Web", slug: "web", }); const subscriptionId = order.subscription.id; const invoiceId = order.invoice.id;
Code
order = api("POST", "/checkout", { "product_id": product["id"], "plan_id": plan["id"], "region_id": region["id"], "name": "Web", "slug": "web", }) subscription_id = order["subscription"]["id"] invoice_id = order["invoice"]["id"]

Add organization_id to bill an organization rather than your personal one, and project_id to file it in a specific project. If you leave project_id out, the organization's default project is used.

4. Pay the invoice

From the wallet:

TerminalCode
curl -s -X POST "$ZAROZ_API/billing/invoices/$INVOICE_ID/pay-with-wallet" -H "Authorization: Bearer $ZAROZ_TOKEN"
TypeScriptCode
await api("POST", `/billing/invoices/${invoiceId}/pay-with-wallet`);
Code
api("POST", f"/billing/invoices/{invoice_id}/pay-with-wallet")

To pay by card or PayPal instead, start the invoice checkout (/billing/invoices/{id}/checkout/mollie or .../paypal) and follow the returned link. Once paid, the subscription activates and provisioning starts on its own.

5. Find the service instance

The subscription is the purchase; the service instance is the thing that runs. Read the subscription for its project_id and its resources for the instance id. Wait until the status is active.

TerminalCode
curl -s "$ZAROZ_API/subscriptions/$SUBSCRIPTION_ID" -H "Authorization: Bearer $ZAROZ_TOKEN" | jq '{status, project_id}' curl -s "$ZAROZ_API/subscriptions/$SUBSCRIPTION_ID/resources" -H "Authorization: Bearer $ZAROZ_TOKEN" | jq '.service_instance.id'
TypeScriptCode
const sub = await api("GET", `/subscriptions/${subscriptionId}`); // wait for sub.status === "active" const projectId = sub.project_id; const res = await api("GET", `/subscriptions/${subscriptionId}/resources`); const serviceId = res.service_instance.id;
Code
sub = api("GET", f"/subscriptions/{subscription_id}") # wait for sub["status"] == "active" project_id = sub["project_id"] res = api("GET", f"/subscriptions/{subscription_id}/resources") service_id = res["service_instance"]["id"]

If the product asks setup questions, GET /service-instances/{id}/setup lists the unanswered steps, and answering the last one starts the first provisioning (PUT /service-instances/{id}/setup/steps/{step}).

6. Say which image to run

Saving the source does not start anything; it only records what the next deployment pulls. Use kind: registry. For a public image, send no credentials.

TerminalCode
curl -s -X PUT "$ZAROZ_API/service-instances/$SERVICE_ID/source" \ -H "Authorization: Bearer $ZAROZ_TOKEN" -H "Content-Type: application/json" \ -d '{"kind":"registry","host":"docker.io","repository":"nginx","tag":"1.27"}'
TypeScriptCode
const source = await api("PUT", `/service-instances/${serviceId}/source`, { kind: "registry", host: "docker.io", repository: "nginx", tag: "1.27", }); console.log(source.image_reference); // docker.io/library/nginx:1.27
Code
source = api("PUT", f"/service-instances/{service_id}/source", { "kind": "registry", "host": "docker.io", "repository": "nginx", "tag": "1.27", }) print(source["image_reference"])

Zaroz qualifies a single-segment Docker Hub name with library/, so nginx works. Use a specific tag; the platform does not re-pull when a tag moves (watch_tag is always false).

7. Deploy

A deployment takes no body. It ships the source exactly as saved.

TerminalCode
curl -s -X POST "$ZAROZ_API/service-instances/$SERVICE_ID/deployments" -H "Authorization: Bearer $ZAROZ_TOKEN"
TypeScriptCode
const deployment = await api("POST", `/service-instances/${serviceId}/deployments`); // poll until the outcome is recorded for (;;) { const d = await api("GET", `/service-instances/${serviceId}/deployments/${deployment.id}`); if (d.outcome) { console.log(d.outcome, d.failure_reason); break; } await new Promise((r) => setTimeout(r, 3000)); }
Code
import time deployment = api("POST", f"/service-instances/{service_id}/deployments") while True: d = api("GET", f"/service-instances/{service_id}/deployments/{deployment['id']}") if d["outcome"]: print(d["outcome"], d.get("failure_reason")); break time.sleep(3)

outcome is succeeded or failed; null means not recorded yet, never a failure. Your Workload now runs one unit.

Next

Last modified on