Skip to main content

Deploy a service

In this guide you deploy a container image as a serverless service, watch it become ready, and learn exactly how much of your project's quota each deploy consumes. It takes about 5 minutes. It also costs nothing while the service is idle, because scale-to-zero means no compute charge until the first request arrives.

Before you begin

  • An account and a project. Ask your administrator for an account or an invitation link — there is no self-service sign-up. See projects and access.
  • A container image, which is a packaged copy of your app and everything it needs to run. Serverless deploys images, not source code. To deploy from source instead, use Functions or Agents, which build images for you. Your image must listen for HTTP on a port; the default is 8080. Is your image private and stored in Crusoe Container Registry? Then map the project to Crusoe Cloud first — see where your image can come from.
  • For the API path: a credential. That is either an API key (see service accounts and API keys) or a 12-hour session token from platformctl login. You send it as a bearer token — the word Bearer, a space, then the credential, in the Authorization header. Details in API authentication.
Use a pullable image reference

A registry is a server that stores container images. Reference your image either from a public registry, or from a private Crusoe Container Registry repository — including the repository the platform built into when it built an image for you. See where your image can come from.

Anything else is accepted at deploy time and then fails minutes later with an image pull error. The machines that run your container cannot reach it.

Where your image can come from

Serverless pulls from two kinds of registry: public ones, which need no setup, and your own private Crusoe Container Registry, which needs the project connected to Crusoe Cloud first. Anything the platform built for you is already in the second kind.

SourceAn image reference looks likeHow an image gets there
Images the platform built for youregistry.us-east1-a.ccr.crusoecloudcompute.com/cai-ab12cd-checkout-api:c5c2f25dYou do not push these. When the platform builds from your source — see Functions, Agents and deploy from source — it writes the image into a repository in your own Crusoe Cloud Registry, named cai-<project-short>-<workload>. Reference it when you want to redeploy an image the platform already built. It is a CCR reference, so the row below applies to it
A public registrydocker.io/library/nginx:1.27You push it wherever you normally do. No setup here, but the image must be readable without a password
Crusoe Container Registry (CCR) — your private imagesregistry.us-east1-a.ccr.crusoecloudcompute.com/checkout-api.7dhg29ls:v1Map the project to Crusoe Cloud first, then push the image to your repository

Private images: map your project to Crusoe Cloud first

A private registry asks for a password, and the platform has no way to guess yours. Mapping your project to a Crusoe Cloud account is how it gets one.

Do this once, in the console. Open your project, choose Project Settings, and enter a Crusoe Cloud access key ID and secret key. The same thing over the API is PUT /v1/projects/{projectID}/crusoe-cloud. That route lives on the platform API, so use $CAI_API, not $CAI_SERVERLESS_API. The Crusoe Cloud integration page walks through it step by step.

Mapping does two things:

  1. It stores your Crusoe Cloud credential for the project.
  2. It creates a hidden image-pull credential in your project, named ccr-pull. The platform builds this credential for you. The username is your Crusoe account email. The password is a short-lived registry token that can do nothing but pull images. Your access key is never used as a registry password, because CCR does not accept it as one.

From then on, every deploy and every edit runs the same check by itself. It asks two questions. Is this project mapped? And is the image's host one that the ccr-pull credential can sign in to?

If both answers are yes, the platform attaches the credential for you. It does that by pointing the service at a ccr-pull service account that carries it. If either answer is no, it attaches nothing and uses your image reference exactly as written. That is why a public image is never touched.

You never create, name, or reference that credential yourself. There is no field on this API for supplying your own registry password.

An unmapped project fails silently

If the project is not mapped, a CCR deploy is still accepted with 202. No credential is attached, and nothing in the response mentions the mapping. Minutes later the image pull fails, and the service never reaches ready. Map the project before you deploy a CCR image.

Already deployed, and mapped afterwards? PATCH the service so the check runs again and the new revision gets the credential. Re-sending the same image is enough.

Two more sharp edges before you rely on CCR:

  • The ccr-pull credential is built only during the mapping call. A repository you create later is not added to it.
  • The token inside the credential expires, which breaks pulls that had worked for weeks.

The fix for both is the same one line: map the project again. Known issues and the Crusoe Cloud integration page have the details.

One last rule. If you set service_account_name yourself, the platform leaves your choice alone and does not attach the CCR credential. Leave that field unset for CCR images. The serverless API reference covers both preconditions in full.

Deploy the service

Only two things are ever required: a name and an image. A name is 1–52 characters of lowercase letters, digits, and dashes, starting with a letter and ending with a letter or digit, and it cannot be changed later. Everything else has a sensible default.

platformctl serverless create checkout-api \
--image registry.us-east1-a.ccr.crusoecloudcompute.com/cai-ab12cd-checkout-api:c5c2f25d \
--env LOG_LEVEL=info
Never pass a credential with --env

An --env value lands in your shell history and shows up in the machine's list of running processes. Use --env-from-secret, which names the secret and nothing else.

Then watch it come up:

platformctl serverless list

You should see:

NAME STATE PUBLISHED URL
checkout-api ready no http://<private-hostname>

platformctl serverless get checkout-api -o json prints the full service object, exactly as the API returns it.

The CLI covers the whole lifecycle: create, update, delete, get, list, logs, metrics, revisions, set-traffic, spec, and triggers. It needs a project set, which you do with --project, the $CAI_PROJECT variable, or platformctl config set-project.

Leave a field out and the platform fills in a default, then echoes it back so you can see what you got:

FieldDefault
port8080
CPU / memory request250m / 512Mi
CPU / memory limit1 CPU / 512Mi
scaling.min_scale / max_scale / container_concurrency0 / 10 / 80
publish.enabledfalse
timeout_seconds300

One more thing happens on every deploy: the platform injects the environment variable CRUSOE_REQUEST_TIMEOUT_SECONDS into your container, matching timeout_seconds.

Update a service

Every edit creates a new revision.

platformctl serverless update checkout-api --image registry.us-east1-a.ccr.crusoecloudcompute.com/cai-ab12cd-checkout-api:9f31ab02
--env on update replaces the whole environment

Unlike the individual fields, --env is not a merge on update — it replaces everything. Resupply every variable you want to keep.

scaling follows the same rule as every other field in a PATCH. Send a scaling block and any sub-field you leave out keeps the service's current value, not a platform default. So a PATCH carrying only {"min_scale": 2} leaves max_scale and container_concurrency exactly as they were.

Create behaves differently, because a brand-new service has no current scaling to keep. There, any sub-field you omit gets the platform default of 0 / 10 / 80.

Read whether it is serving

Everything the platform observed about your service sits at the top level of the object, alongside the settings you sent. There is no status block to open — the readiness answer is spelled the same way here as on every other resource on the platform.

Four things tell you almost everything:

  • state — the one-word summary, always lowercase. pending means the platform is still working toward your spec. ready means it is serving as requested. not_ready means it is not serving. degraded means it is serving, but not with every setting you asked for. invalid means the spec was rejected. This is the word to show a person.
  • ready — a plain true/false, and the field to branch on in a script. It is true only for state: ready. When it is false, message carries one sentence saying why; when it is true, message is left out entirely.
  • conditions — the named pass/fail checks behind the state: Ready, ServiceReady, VisibilityEnforced, RuntimeClassApplied, TrafficAccepted, and Exposed. message is the Ready condition's own words; when you need more than one sentence, this is where the rest is.
  • generation vs. observed_generationgeneration counts your edits; observed_generation counts the ones the platform has applied. If they differ, your latest change was accepted but has not taken effect yet. The console shows a notice for this state.

One more pair is worth knowing about, because the two look alike and mean different things. traffic is the split you asked for. active_traffic is the split actually being served right now. They match once a change has rolled out, and differ while one is on its way — providing real-time visibility into traffic routing status.

The console's service detail page shows all of this on its Overview tab. Three more tabs sit beside it: Revisions, which holds the traffic controls and one-click rollback; Logs; and YAML, which prints the service definition with secret values removed.

Work out what one service costs your quota

A quota is a cap on how much of something your project may use at once. Every project starts with these: 50 running instances, 30 services, 10 CPU / 20 GiB reserved, and 20 CPU / 40 GiB maximum. The services cap is the one that surprises people, because of how revisions work:

  • Every revision of a serverless service permanently holds 2 services, which are internal routing objects. It holds them for as long as the revision exists.
  • Every deploy and every edit creates a new revision.

So a service you deployed and then updated once has 2 revisions, which hold about 4 of your 30 services. The count keeps growing with every edit until old revisions are cleaned up.

At 30 of 30, new revisions jam across the whole project with SKSReady=NotReady: No Private Service Name. Nothing becomes ready again until revisions are removed. See troubleshooting for the fix, and quotas and audit for managing limits.

Your project's live number is always the authority. Read it on the console's Quotas page, or from GET /v1/projects/{projectID}/quota. That route is on the platform API, so use $CAI_API, not $CAI_SERVERLESS_API; see API authentication. See all platform limits.

The CPU/memory trap

Set a CPU or memory request but no limit, and the platform stamps a default limit of 2 CPU / 4 GiB onto your container. It stamps the same limit onto the small helper container that handles your service's networking, which quietly drains your quota. You find out later, as a FailedCreate error. Either set both request and limit, or set neither and take the platform defaults (250m/512Mi request, 1 CPU/512Mi limit).

Clean up

Deleting a service requires the project admin role. Members get a 403, and this is the only place this API uses 403. A delete removes the endpoint, releases its public hostname, and frees the quota held by all of its revisions.

platformctl serverless delete checkout-api

Next steps