/environments.md. The full docset is at /llms-full.md and the index is at /llms.md.Environments
C3 runs your script inside a container built from the Docker image named in
.c3, delivered through Nydus, with a persistent JuiceFS workspace. Pick a
public image that already contains everything your script needs:
| Your project | Image |
|---|---|
| Python with GPU libraries | A framework image such as pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime |
| System packages, other languages, custom CUDA | Any public Docker Hub or GitHub Container Registry image that contains them |
| Self-contained binaries or Bash scripts | A base image such as ubuntu:24.04 |
Jobs run without outbound network access, so the script cannot install packages. Anything the image lacks has to be built into a custom image.
Docker
project: docker-example
script: run.sh
hardware: l40
time: "02:00:00"
docker:
image: pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime
requires_accelerator: cuda
output:
- ./results
# run.sh
#!/bin/bash
set -euo pipefail
python3 train.py --output "$C3_ARTIFACTS_DIR"
C3 prepares the image once per immutable image/platform and reuses its Nydus
representation. It runs bash /workspace/run.sh with your persistent workspace,
attached data resources and $C3_ARTIFACTS_DIR mounted. Select
--environment-delivery=oci to use ordinary container delivery instead.
Images on the GitHub Container Registry work the same way:
docker:
image: ghcr.io/my-lab/trainer:1.4
requires_accelerator: cuda
A CPU job that only needs a shell and the tools in a base image:
project: bash-example
script: run.sh
hardware: cpu-d3-4vcpu-16gb
time: "00:30:00"
docker:
image: ubuntu:24.04
requires_accelerator: none
output:
- ./results
# run.sh
#!/bin/bash
set -euo pipefail
mkdir -p results
./bin/my-simulation --output results/output.dat
Requirements:
- Without a registered credential reference, use a public image on Docker Hub or
ghcr.io.ubuntu:24.04,user/image:tag,docker.io/...andghcr.io/<owner>/<image>:<tag>are accepted, with a tag or a digest. Other registries are rejected before upload. Anonymous pulls require a public package; GHCR reports private or missing packages as denied. - C3 reuses an image already present on the machine. Pin a digest if you need a specific build rather than a mutable tag such as
latest. - The image must contain
bash. Alpine and distroless images often do not. - The image must already contain every dependency. Jobs run in a private network namespace with no outbound access, so
pip installorapt-getin the script fails. - Set
requires_accelerator: cudafor GPU images andnonefor CPU images. C3 stops before running the script if the machine does not match. - C3 preserves the verified image environment for the payload, while fixing workspace, home and cache destinations for isolation.
If C3 has registered a private registry credential for your organization, pass its reference alongside the image:
docker:
image: registry.example.com/team/trainer:1.4
credential_id: my-registered-reference
credential_id is a nonsecret reference, not a password or token. The server
checks its organization, registry and repository before preparing the image.
A refusal fails the submission; C3 never retries it anonymously. This requires
JuiceFS storage and works with Nydus or ordinary OCI delivery. It cannot be used
with legacy storage or c3 ar deploy. MCP deploy accepts the same
reference as docker_credential_id. Public images need no new setting.
For a CPU offering without the persistent runtime, use --storage=legacy and set requires_accelerator: none. See CPU jobs.
Container contents and persistence
The image supplies Python, system libraries and command-line tools; C3 adds
nothing to it except GPU access. GPU jobs receive the assigned GPU devices, the
driver libraries and nvidia-smi through the isolated runtime. LD_LIBRARY_PATH
and TRITON_LIBCUDA_PATH point at the mounted driver, so Triton and other tools
that read the linker cache find libcuda.so.1. An LD_LIBRARY_PATH set by your
image is kept and the driver directories are appended to it.
Files under /workspace and attached writable data resources persist between
jobs. Changes to the container root, /tmp or the job home are temporary.
Every job runs with a memory, CPU and process limit set for its hardware
offering. /tmp is a small memory-backed filesystem (256 MiB on current
offerings) and anything written there counts against the job's memory limit.
Keep downloads, model caches and scratch files under the job home or
/workspace; TMPDIR and the cache variables below already point there.
Model weights that every run needs belong in a dataset (c3 data upload), so
they are mounted instead of downloaded on each job.
--storage=legacy explicitly selects the older execution/storage pathway for
compatible offerings. It is never selected automatically after a runtime failure.
Variables available to your script
| Variable | Value |
|---|---|
| Working directory | /workspace |
C3_ARTIFACTS_DIR | /artifacts |
HOME | /home/job (temporary, disk-backed) |
TMPDIR | /home/job/tmp (temporary, disk-backed) |
HF_HOME, TORCH_HOME | /home/job/.cache/huggingface, /home/job/.cache/torch (temporary) |
UV_HTTP_TIMEOUT | 300 (seconds per package request during dependency install) |
The environment is sanitised: no C3 credentials or storage keys are visible to your script. .env files in your workspace are uploaded but not loaded; load them yourself if you need them.