A Container is one rung above a Sandbox: same gVisor-isolated runtime underneath, but wrapped with image building, local file upload, secret injection, and an exec-style invocation API. Use a container for one-off batch jobs, interactive sessions, or long-running processes that don’t need replication.

If you need replicated HTTP-fronted workloads with autoscaling, use a Scaling Group instead. If you need a serverless, fan-out invocation model with a Python function as the interface, use Functions.


Quick start

from chalkcompute import Container, Image

c = Container(
    image=Image.debian_slim("3.12").pip_install(["requests"]),
    name="hello-container",
    cpu="1",
    memory="2Gi",
).run()

result = c.exec("python", "-c", "import requests; print(requests.__version__)")
print(result.stdout_text)
print(f"exit code: {result.exit_code}")

c.stop()

Container(...).run() does three things in sequence:

  1. Builds the Image (or skips if the spec is already cached, since images are content-addressed).
  2. Uploads any local files declared with add_local_file / add_local_dir to a volume and mounts it into the container.
  3. Creates the container and polls until it reaches the Running state.

.run() returns self, so you can chain it.


Configuring resources

CPU, memory, and GPU are passed at construction time and follow Kubernetes conventions:

c = Container(
    image=Image.debian_slim("3.12"),
    name="batch-job",
    cpu="4",           # cores, e.g. "500m" for half a core
    memory="16Gi",     # Mi / Gi suffixes
    gpu="nvidia-l4:1", # type:count, or just count
).run()

If you omit these flags, the service applies defaults from your environment’s resource configuration. GPU support and the set of available GPU types depend on the node pools provisioned in your cluster — see Sandbox § GPU.


Environment variables and secrets

Inject configuration via env (plaintext) or secrets (resolved from Chalk’s managed secret store, an integration, or your local environment):

from chalkcompute import Container, Image, Secret

c = Container(
    image=Image.debian_slim("3.12"),
    name="api-client",
    env={"LOG_LEVEL": "INFO"},
    secrets=[
        Secret.from_env("OPENAI_API_KEY"),
        Secret.from_integration("prod_postgres"),
    ],
).run()

See the Secrets section of the Compute overview for the full list of Secret constructors.


Mounting volumes

Attach a Volume to share durable state between runs or expose a large dataset without baking it into the image:

from chalkcompute import Container, Image, Volume

vol = Volume("training-data")
vol.put_file("inputs/example.txt", b"hello\n")

c = Container(
    image=Image.debian_slim("3.12"),
    name="trainer",
    volumes=[("training-data", "/data")],
).run()

result = c.exec("cat", "/data/inputs/example.txt")
print(result.stdout_text)  # "hello\n"

Volumes mounted this way are persistent across container restarts and shared across other containers that mount the same volume.


Executing commands

Container.exec(*command, timeout_secs=...) runs a one-shot process inside the already-started container, blocking until the command finishes:

result = c.exec("ls", "-la", "/app")
print(result.stdout_text)
print(result.stderr_text)
print(result.exit_code)

exec captures stdout and stderr in full and returns them at the end. If you need streaming output, stdin, or signal handling, drop down to the lower-level Sandbox API — Container is a wrapper over the same primitive.


Exposing a port

If your container runs a long-lived server (HTTP, gRPC, etc.) that you want to reach from inside the cluster, pass port:

c = Container(
    image=Image.debian_slim("3.12").pip_install(["flask"]),
    name="api",
    port=8080,
    entrypoint=["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=8080"],
).run()

For externally addressable, replicated, autoscaling HTTP services, prefer a Scaling Group — it gives you a public DNS name, TLS termination, and replica management out of the box.


Lifecycle management

Attaching to an existing container

c = Container.from_name("hello-container")
# or
c = Container.from_id("550e8400-e29b-41d4-a716-446655440000")

This is useful for reconnecting to a long-lived container from a different process without re-running .run() (which would attempt to recreate it).

Inspecting status

c.refresh()      # re-fetch from the server
print(c.info.status)  # 'Running', 'Failed', etc.
print(c.id)
print(c.image_uri)

Stopping

c.stop()                          # immediate stop + cleanup
c.stop(grace_period_seconds=30)   # SIGTERM, wait 30s, then SIGKILL

stop() also removes any temporary volumes created from add_local_file uploads and deletes any secrets that were upserted from your local environment for this run (e.g. via Secret.from_local_env).


When to use a Container vs other compute primitives

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