Chelabase
Services

Container services

Run a service as a container Chelabase starts for you, how a new image rolls over, and what the runner does and doesn't isolate.

Alpha

Chelabase is early, pre-release software and not ready for production data. Expect breaking changes between versions, rough edges and security issues no one has found yet. Run it on data you can afford to lose, and report what breaks.

A container service is an image Chelabase pulls and runs for you through the service runner. It is routed at /services/v1/{name}/* with the same signed headers as an external service, health-checked, and switched on and off per branch. Containers need the services compose profile: see Self-hosting functions and services.

Container deploys are tenant-admin only, and the runner puts no egress filter on its containers. Read What the runner doesn't isolate before you let anyone deploy an image.

Deploy

chelabase services deploy NAME --image <name:tag|name@sha256:…> --port <1-65535> [--health PATH] [--memory 256|256m|1g] [--cpus 0.5]

chelabase services deploy echo --image registry.example.com/echo@sha256:… --port 8080 --health /healthz

The first deploy creates the service and prints its signing secret once (echo: signing secret whsec_… (shown only now); keep it for verifying requests). Every deploy prints NAME: deployed <id>, then waits for a background rollover and exits non-zero if it fails or a newer deployment replaces it. The first deploy then turns the service on. --memory is MiB, or a size with an m or g suffix; --memory and --cpus set resources below.

CommandWhat it does
services listThe branch's services: name, enabled or disabled, health, active deployment, last error.
services enable NAMETurns the service on for this branch.
services disable NAMETurns it off: calls answer 503 and its container stops.
services logs NAMEThe container's last 200 log lines.
services rollback NAME DEPLOYMENTRolls back to an earlier deployment, once its container is healthy (a rollover, like a deploy).

The deployment's spec is {backend: "container", image, port, health_path, resources}:

  • image is name@sha256:<64 hex> or name:tag. The orchestrator resolves a tag to a digest through the runner before storing, so what runs is what was checked.
  • port is the port the service listens on inside the container (it is also given to the container as PORT). health_path defaults to /.
  • resources may lower memory_mb and cpus, never raise them past the runner's caps (CHELABASE_SERVICE_RUNNER_MEMORY_CAP_MB, default 512, and CHELABASE_SERVICE_RUNNER_CPU_CAP, default 1).
  • Unknown spec fields are refused, so a spec can't ask for privileged, a mount or a network.

Studio's Functions tab lists services beside the functions: a service kind badge with the backend (container or external) under it, a Status (Running, Unhealthy, Starting, Not deployed, Stopped) and the enabled switch. A container service's drawer has a Logs section with the last 200 lines and a Refresh button (Developer and up), and a notice while a rollover is pending or after one failed.

Rollover

A new image reference is a new deployment, and it rolls over without downtime:

  1. the new container is started and checked until it is healthy (60 seconds at most, then the rollover fails);
  2. the route switches to it;
  3. the old container is stopped.

The rollover runs in the background: the deploy (or rollback) of a service that is enabled on the branch answers 202 with rollout: pending, and the service's rollout field in the API says pending, then disappears once the new deployment is active, or turns failed with the reason (rollout.state: "failed", rollout.error). chelabase services deploy and rollback wait for it, and Studio shows it on the service.

If the new image never passes its health check, traffic stays on the old container, the service's last_error on the branch says why, rollout.state is failed with the reason, and the deploy command exits non-zero.

Switch off and logs

Turning a service off answers 503 within about 2 seconds and stops its container within about 30 seconds. chelabase services logs and Studio's logs panel show the container's last 200 lines, as text, from GET …/functions/{name}/logs:

  • Developers and above may read them; a Viewer gets 403 (logs can hold what a service prints).
  • 404 when the service has no running container on the branch, 409 conflict with reason: not_deployed when it has no deployment there, 422 for a function or an external service.

With the services profile off, a container deploy, rollback or logs read answers 503 runner_unavailable, container services don't run, and external services keep working.

Environment

A container gets the service's secrets on the branch as environment variables, plus:

VariableValue
PORTThe spec's port
CHELABASE_URLThe instance's public API URL
CHELABASE_BRANCH_IDThe branch the container serves

How the runner works

The service runner is the only component that starts member containers. It holds the Docker socket, so it is narrow on purpose:

  • It acts only on containers labelled with its own stack and a service, checked on every call; any other container (a compute, another stack's) is "not found", even by id.
  • Every container it creates is hardened the same way: non-root, read-only root file system, every Linux capability dropped, no-new-privileges, memory, CPU and pids limits, no host mount, no device, no host namespace.
  • Each service gets a private network of its own, which only the stack's functions container joins, so service containers never reach one another and traffic flows only from the functions service to the service.
  • Only the "ensure" call creates a container (with the branch's secrets as its environment); the reconciler (every CHELABASE_SERVICE_RUNNER_RECONCILE_SECS, default 30) starts, stops and removes.
  • Its two callers, the functions service and the worker, each present their own bearer token.
  • Environment: at most 128 entries per container, 32 KiB per value, 256 KiB in all.

What the runner doesn't isolate

A container's network has no egress filter. From a service container you can reach whatever the Docker host's network reaches: host-side private and link-local addresses (the host's own listeners, other machines on your LAN, a cloud metadata address), as well as the public internet. The container can't reach the other stack services (it shares only its own network with the functions container), but the host and your network are open. Treat an image as trusted code on your network, and keep the privilege to deploy one with tenant admins.

Pulling an image also happens from the Docker host's network. The runner refuses a registry whose host is, or resolves to, a non-public address (loopback, private, link-local, localhost). That narrows pull-side SSRF but can't close it: a registry can still redirect the engine through its token realm to somewhere else. This is the other reason deploys are tenant-admin only.

On this page