# Environments

Every job runs your `script` as Bash on a fresh machine. The environment mode decides what C3 installs first.

| Your project                                                | Use       | C3 prepares                                                                        |
| ----------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------- |
| Python with a `pyproject.toml`                              | `python:` | A `uv` virtualenv from `uv.lock`, cached across jobs                               |
| Anything with system packages, other languages, custom CUDA | `docker:` | A public Docker Hub or GitHub Container Registry image, your script runs inside it |
| Self-contained binaries or scripts with no setup            | neither   | Nothing. The script runs directly on the machine                                   |

`python:` and `docker:` cannot be combined. Setup done inside the script itself runs again on every job.

## Python[​](#python "Direct link to Python")

```
project: python-example
script: run.sh
hardware: l40
time: "02:00:00"

python:
  project: ./

output:
  - ./results
```

```
# run.sh
#!/bin/bash
set -euo pipefail
python3 train.py --output "$C3_ARTIFACTS_DIR"
```

```
# pyproject.toml
[project]
name = "python-example"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["jax[cuda12]", "numpy"]
```

C3 runs `uv sync` before your script and caches the environment by the lock file, so repeat jobs skip the install. Create or refresh the lock file locally:

```
uv lock
```

If you only have a `requirements.txt`, create a `pyproject.toml` with `uv init` and `uv add -r requirements.txt`, then `uv lock`. If the project is in a subdirectory, point `python.project` at it.

## Docker[​](#docker "Direct link to 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
```

C3 pulls the image on the machine and runs `bash /workspace/run.sh` inside it, with your workspace, datasets and `$C3_ARTIFACTS_DIR` mounted.

Images on the GitHub Container Registry work the same way:

```
docker:
  image: ghcr.io/my-lab/trainer:1.4
  requires_accelerator: cuda
```

Requirements:

* A public image on Docker Hub or `ghcr.io`. `ubuntu:24.04`, `user/image:tag`, `docker.io/...` and `ghcr.io/<owner>/<image>:<tag>` are accepted, with a tag or a digest. Other registries are rejected before anything is uploaded.
* The package must be public. C3 pulls anonymously and does not support private images on either registry. On GHCR, set the package visibility to public in GitHub package settings; GitHub reports a private or missing package 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.
* Set `requires_accelerator: cuda` for GPU images and `none` for CPU images. C3 stops before running the script if the machine does not match.
* The image keeps its own `PATH`, `HOME` and `ENV`. C3 adds only the job variables below.

For a CPU job, request a CPU profile and set `requires_accelerator: none`. See [CPU jobs](https://docs.cthree.cloud/hardware.md#cpu-jobs).

## Bash[​](#bash "Direct link to Bash")

```
project: bash-example
script: run.sh
hardware: l40
time: "00:30:00"

output:
  - ./results
```

```
# run.sh
#!/bin/bash
set -euo pipefail
mkdir -p results
./bin/my-simulation --output results/output.dat
```

Whatever the script installs is installed again on every job. Move that setup into `python:` or `docker:` when startup time matters.

## What is on the machine[​](#what-is-on-the-machine "Direct link to What is on the machine")

Jobs run on Ubuntu virtual machines managed by C3's agent. C3 stages your workspace and prepares the configured Python or Docker environment for each job. Machines may be reused between jobs; do not rely on setup from a previous job.

|                                | GPU machines                                                                              | CPU machines |
| ------------------------------ | ----------------------------------------------------------------------------------------- | ------------ |
| Ubuntu                         | 22.04 or 24.04, depending on the provider                                                 | 24.04        |
| NVIDIA driver and CUDA toolkit | Yes. CUDA 12.2 or 13.0, depending on the provider                                         | No           |
| Python                         | The Ubuntu system `python3` (3.10 or 3.12). `pip` and `venv` are not guaranteed; use `uv` | Same         |
| `uv`                           | Yes, on the `PATH`                                                                        | Yes          |
| Docker                         | Yes, with the NVIDIA container runtime                                                    | Yes          |
| Also present                   | `bash`, `curl`, `tar`, `gzip`, `zstd`                                                     | Same         |

Nothing else is guaranteed. Do not rely on `git`, a compiler or conda being present. In Python mode, put dependencies in `pyproject.toml`. In Docker mode, put them in the image. In Bash mode, install them in the script and accept the startup cost.

## Variables available to your script[​](#variables-available-to-your-script "Direct link to Variables available to your script")

| Variable              | Value                                                                 |
| --------------------- | --------------------------------------------------------------------- |
| `C3_JOB_WORKDIR`      | Absolute path of your uploaded workspace, the working directory       |
| `C3_ARTIFACTS_DIR`    | Directory whose contents are collected as results. Always exists      |
| `C3_HARDWARE_PROFILE` | Exact profile of the machine, such as `l40s` or `cpu-d3-96vcpu-384gb` |
| `C3_HARDWARE_KIND`    | `gpu` or `cpu`                                                        |
| `C3_ACCELERATOR_KIND` | `cuda` or `none`                                                      |
| `PYTHONUNBUFFERED`    | `1`, so Python output streams to the logs promptly                    |
| `VIRTUAL_ENV`         | Set in Python mode                                                    |
| `TMPDIR`              | Per-job temporary directory, Python and Bash modes                    |

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.
