A scaling group is a replicated, HTTP-fronted service managed by Chalk. Each group is backed by a Kubernetes Deployment with rolling updates, automatic service discovery, and CPU-utilization-based autoscaling between configurable min and max replica counts. Use scaling groups for ML inference servers, internal APIs, agent backends, and any long-lived service that needs to be reachable from outside the cluster.

For workloads that don’t need replication or HTTP fronting, use a Sandbox instead. For serverless, function-shaped invocations, use Functions.

For registered models served through Chalk’s Arrow inference contract, use the Model Deployments APIs. They expose typed deployment objects, readiness waiting, revision history, updates, and rollback.

You can drive scaling groups two ways: the Python SDK (chalkcompute.ScalingGroup) or the chalk scaling-group CLI. Both surfaces target the same underlying service, so groups created one way are visible and manageable from the other.


​
Quick start (SDK)

Save a small Flask app as app.py:

from flask import Flask

app = Flask(__name__)


@app.get("/health")
def health():
    return {"ok": True}


app.run(host="0.0.0.0", port=8080)

Then deploy it from a script in the same directory:

import chalkcompute

img = (
    chalkcompute.Image.debian_slim("3.12")
    .pip_install(["flask"])
    .add_local_file("./app.py", "/app/app.py")
    .entrypoint(["python", "/app/app.py"])
)

sg = chalkcompute.ScalingGroup(
    image=img,
    name="hello-api",
    port=8080,
    min_replicas=1,
    max_replicas=3,
    startup_probe=chalkcompute.StartupProbe.http("/health"),
).deploy()

resp = sg.call("/health", method="GET")
print(resp.status, resp.text())

sg.delete()

ScalingGroup(...).deploy() builds the image, uploads any local files declared via add_local_file / add_local_dir, creates the scaling group, and waits until at least one replica is ready and available. With startup_probe set, deploy() also waits until the group’s web_url responds at /health.


​
Quick start (CLI)

$ chalk scaling-group create \
    --image=my-registry/my-inference-server:v1 \
    --name=inference \
    --max-replicas=1 \
    --port=8080

This creates a Kubernetes Deployment with a single replica running your image, a headless Service, and an HTTPRoute that exposes the service over HTTPS.

You can tune replicas and resource limits at creation time:

$ chalk scaling-group create \
    --image=my-registry/my-inference-server:v1 \
    --name=inference \
    --port=8080 \
    --min-replicas=3 \
    --max-replicas=3 \
    --cpu=4 \
    --memory=8Gi

​
Revisions and rollback

Each deploy records an immutable revision of the group’s spec. The scaling group keeps a stable ID and name; later deploys append a revision and serve it, rather than creating a second group.

Redeploying with the same name from ScalingGroup(...).deploy() or chalk scaling-group create records a new revision and routes all traffic to it. That is how you roll a new image or spec onto a running group.

To roll back, select an earlier revision so the group serves that spec again. A group serves one revision at a time. Revision history is on the scaling group’s Revisions tab in the dashboard.


​
Replicas and autoscaling

The SDK exposes the autoscaler controls directly on the ScalingGroup constructor:

ParameterDefaultDescription
min_replicas1Lower bound for the autoscaler. Set to 0 to allow scale-to-zero.
max_replicas1Upper bound for the autoscaler.
target_cpu_utilization_percentageunset (cluster default)Average CPU utilization the autoscaler targets.
scaling_interval_secondsunsetHow often the autoscaler re-evaluates.
shutdown_delay_secondsunsetGrace period when removing replicas, to drain in-flight requests.
sg = chalkcompute.ScalingGroup(
    image=img,
    name="inference",
    port=8080,
    min_replicas=2,
    max_replicas=20,
    target_cpu_utilization_percentage=70,
    shutdown_delay_seconds=30,
).deploy()

From the CLI, --min-replicas and --max-replicas set the replica range, and --target-cpu sets the CPU utilization target for autoscaling. --max-replicas is required. To run a fixed number of replicas, set both flags to the same value.


​
CPU and memory resources

The --cpu and --memory flags (CLI) and the cpu / memory constructor arguments (SDK) control the resource requests and limits for each replica. When specified, both the Kubernetes request and limit are set to the same value, guaranteeing your workload the resources it asks for.

$ chalk scaling-group create \
    --image=my-registry/my-app:v1 \
    --name=my-service \
    --max-replicas=1 \
    --port=8080 \
    --cpu=2 \
    --memory=4Gi

If you omit CPU and memory, each replica gets 250m CPU and 512Mi memory, set as both the request and the limit.

CPU values follow Kubernetes conventions: 1 means one full core, 500m means half a core. Memory values use standard suffixes: Mi (mebibytes) and Gi (gibibytes).


​
GPU support

Scaling groups support GPU-accelerated workloads. The SDK and CLI both accept a type:count value (or just count) for the GPU request:

sg = chalkcompute.ScalingGroup(
    image=img,
    name="gpu-inference",
    port=8080,
    cpu="8",
    memory="32Gi",
    gpu="nvidia-l4:1",
    min_replicas=1,
    max_replicas=4,
).deploy()
$ chalk scaling-group create \
    --image=my-registry/my-gpu-model:v1 \
    --name=gpu-inference \
    --max-replicas=1 \
    --port=8080 \
    --gpu=nvidia-tesla-t4:1 \
    --cpu=4 \
    --memory=16Gi

The type portion selects the GPU node the replicas are scheduled on, and the count sets the number of GPUs each replica gets (the nvidia.com/gpu request and limit). Chalk adds the nvidia.com/gpu toleration automatically, so replicas can schedule onto GPU-tainted nodes.

Write type as a GKE accelerator name on both GCP and AWS:

  • GCP: the value is used as the cloud.google.com/gke-accelerator node selector, so any accelerator in your cluster’s node pools works.
  • AWS: Chalk maps the value to Karpenter’s karpenter.k8s.aws/instance-gpu-name label. The types in the table below are supported.
GPU typeGPU
nvidia-tesla-t4NVIDIA T4
nvidia-a10gNVIDIA A10G
nvidia-l4NVIDIA L4
nvidia-l40sNVIDIA L40S
nvidia-tesla-v100NVIDIA V100
nvidia-tesla-a100NVIDIA A100
nvidia-a100-80gbNVIDIA A100 80GB
nvidia-h100-80gbNVIDIA H100 80GB
nvidia-h100-mega-80gbNVIDIA H100 Mega
nvidia-h200-141gbNVIDIA H200
nvidia-rtx-pro-6000NVIDIA RTX PRO 6000
nvidia-b200NVIDIA B200

Which types you can schedule depends on the GPU node pools configured in your cluster.

To run on any available GPU node without selecting a type, pass only the count:

$ chalk scaling-group create \
    --image=my-registry/my-gpu-model:v1 \
    --name=gpu-inference \
    --max-replicas=1 \
    --port=8080 \
    --gpu=1

To request multiple GPUs, increase the count:

$ chalk scaling-group create \
    --image=my-registry/my-training-server:v1 \
    --name=multi-gpu \
    --max-replicas=1 \
    --port=8080 \
    --gpu=nvidia-tesla-a100:4

​
DNS and routing

Every scaling group is automatically assigned a TLS-terminated DNS hostname through the cluster’s gateway. The pattern is:

<environment-id>-<scaling-group-name>.<gateway-domain>

For example, a scaling group named inference in environment abc123 with gateway domain gw.chalk.ai would be reachable at:

https://abc123-inference.gw.chalk.ai

From the SDK, read it off the deployed group:

print(sg.web_url)
# https://abc123-inference.gw.chalk.ai

From the CLI, the URL is shown as Web URL in the output of chalk scaling-group get and chalk scaling-group list. Traffic arriving at this hostname is routed to the port you specified with --port (or the port constructor argument).

Because the hostname includes the scaling group name, you can reference it by a stable, human-readable URL rather than an opaque UUID.

​
Invoking from the SDK

The .call() helper sends a request to a path under web_url. The response it returns has status, headers, and content attributes, plus text() and json() methods. Use .call() for ad-hoc requests from the SDK, and send production traffic to web_url directly.

resp = sg.call(
    "/predict",
    method="POST",
    json={"text": "the cat sat on the mat"},
    timeout=60.0,
)
print(resp.json())

​
Environment, secrets, and volumes

These use the same environment, secret, and volume configuration patterns as sandboxes:

import chalkcompute

sg = chalkcompute.ScalingGroup(
    image=img,
    name="api",
    port=8080,
    env={"LOG_LEVEL": "INFO"},
    secrets=[chalkcompute.Secret.from_chalk_env("OPENAI_API_KEY")],
    volumes=[("training-data", "/data")],
    min_replicas=1,
    max_replicas=3,
).deploy()

Secrets are resolved at deploy time and injected as environment variables in every replica. Volume mounts are shared across all replicas, so use them for read-mostly data — if multiple replicas write to the same volume path concurrently, the last writer wins on the next sync.


​
Managing scaling groups

​
Listing

$ chalk scaling-group list

Displays a table of all scaling groups in your environment with their ID, name, selected revision, image, status, replica counts, URL, and creation time.

from chalk.client import ChalkClient

client = ChalkClient()
response = client.list_scaling_groups()

for scaling_group in response.scaling_groups:
    print(f"Scaling group: {scaling_group.name}")
    print(f"  Status: {scaling_group.status}")

​
Inspecting

$ chalk scaling-group get --name=inference

Shows the group’s ID, name, selected revision, status, image, port, minimum and maximum replicas, web URL, and creation time.

sg = chalkcompute.ScalingGroup.from_name("inference")
# or
sg = chalkcompute.ScalingGroup.from_id("scaling-group-id")

info = sg.refresh()
print(info.status)             # 'Available', 'Pending', 'ScaledToZero', ...
print(info.ready_replicas)
print(info.available_replicas)
print(sg.web_url)

A scaling group reports one of these statuses:

StatusMeaning
PendingNo replica is ready.
AvailableAll desired replicas are ready.
DegradedSome replicas are ready, but not all desired replicas are, or a rollout has stalled.
ScaledToZeromin_replicas is 0 and the autoscaler has scaled the group to zero replicas.
UnhealthyNo replica is ready and a replica is failing, or a startup probe failed.
ErrorChalk could not apply the group’s spec.
UnknownChalk has not reported a status for the group.
DeletedThe group was deleted.

​
Deleting

$ chalk scaling-group delete --name=inference
sg.delete()

Both surfaces remove the Kubernetes Deployment, Service, and HTTPRoute associated with the scaling group. The SDK additionally cleans up any temporary volumes created from add_local_file uploads. The database record is soft-deleted so the group still appears in history.


​
Tags

You can attach arbitrary key-value tags to a scaling group for organization and filtering:

$ chalk scaling-group create \
    --image=my-registry/my-app:v1 \
    --name=my-service \
    --max-replicas=1 \
    --port=8080 \
    --tags="team=ml,version=2.1"

Tags are applied as Kubernetes labels with the prefix chalk.ai/tag-, making them visible in standard Kubernetes tooling.


​
Entrypoint override

To override the image’s default entrypoint, pass entrypoint from the SDK or --entrypoint from the CLI:

sg = chalkcompute.ScalingGroup(
    image="python:3.12",
    name="custom-server",
    port=8000,
    entrypoint=["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"],
).deploy()
$ chalk scaling-group create \
    --image=python:3.12 \
    --name=custom-server \
    --max-replicas=1 \
    --port=8000 \
    --entrypoint="python,-m,uvicorn,main:app,--host,0.0.0.0,--port,8000"

The CLI takes comma-separated arguments; the SDK takes a list.


​
Full example

A GPU-accelerated inference service that loads model weights from a Chalk Volume, with custom resource limits, multiple replicas, autoscaling, and tags:

$ chalk scaling-group create \
    --image=my-registry/llm-serving:v3 \
    --name=llm-inference \
    --port=8080 \
    --min-replicas=2 \
    --max-replicas=4 \
    --gpu=nvidia-l4:1 \
    --cpu=8 \
    --memory=32Gi \
    --volume=chalk://llama3-weights=/models/llama3 \
    --tags="model=llama3,team=ml-platform" \
    --entrypoint="python,-m,vllm.entrypoints.openai.api_server,--model,/models/llama3"

After creation, the service is reachable at its assigned DNS name and can be referenced by resolvers or other services running in the same Chalk environment.


​
When to use a Scaling Group vs other compute primitives

See Choosing the right primitive on the Compute overview for the full decision table.