# Environment

Every C3 job runs the `script:` from your [`.c3` project configuration](https://docs.cthree.cloud/submission.md) as a Bash script on a GPU VM. The environment mode controls what C3 prepares before that script starts:

* **Python**: C3 builds and caches a uv environment from `pyproject.toml` + `uv.lock`.
* **Docker**: C3 pulls the Docker Hub image in `docker.image` and runs your script inside it.
* **Bash**: C3 runs your script directly on the VM. This can launch anything, but any setup you do in the script runs from scratch on every job.

Use Python or Docker when dependency setup is expensive or needs to be reproducible across jobs. Use Bash when your workload is already self-contained, has trivial setup, or you intentionally want to manage everything inside the script.

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

Use `python.project` for Python projects with a `pyproject.toml` and `uv.lock`:

```
# .c3
project: python-example
script: run.sh
gpu: 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"
requires-python = ">=3.11"
dependencies = [
    "jax[cuda12]",
    "numpy",
]
```

C3 uses [uv](https://github.com/astral-sh/uv) to run `uv sync` before your script starts. The resulting environment is cached from the lock file, so repeat jobs with the same `uv.lock` avoid rebuilding dependencies. If your project is in a subdirectory, set `python.project` to that path.

Generate or refresh the lock file locally with:

```
uv lock
```

Prefer `python.project` to `pip install` in `run.sh`

If you install Python packages inside the Bash script, those installs happen again for every job. Declaring the project with `python.project` lets C3 cache the environment between jobs.

## Docker[​](#docker "Direct link to Docker")

Use `docker:` for non-Python workloads, mixed-language projects, custom CUDA/system libraries, or Python projects that need OS-level dependencies. `docker:` and `python:` are mutually exclusive.

Reference a Docker Hub image from `.c3`:

```
# .c3
project: docker-example
script: run.sh
gpu: l40
time: "02:00:00"

docker:
  image: rust:1.95-slim-bookworm

output:
  - ./results
```

```
# run.sh
#!/bin/bash
set -euo pipefail

cargo run --release -- --output "$C3_ARTIFACTS_DIR"
```

```
# Cargo.toml
[package]
name = "docker-example"
version = "0.1.0"
edition = "2021"
```

```
// src/main.rs
use std::{env, fs, path::PathBuf};

fn main() -> std::io::Result<()> {
    let mut output = None;
    let mut args = env::args().skip(1);
    while let Some(arg) = args.next() {
        if arg == "--output" {
            output = args.next();
            break;
        }
    }

    let output = output.unwrap_or_else(|| {
        env::var("C3_ARTIFACTS_DIR").unwrap_or_else(|_| ".".to_string())
    });
    let path = PathBuf::from(output).join("result.txt");
    fs::create_dir_all(path.parent().unwrap())?;
    fs::write(path, "Rust job ran on C3\n")
}
```

Docker image requirements

Use a public Docker Hub image that C3 can pull without authentication; bare names (`ubuntu:22.04`), namespaced names (`user/image:tag`), and explicit `docker.io/...` hosts are accepted, but private repositories and external registries are not. Keep secrets out of image layers: do not publish `.c3.local`, `.env`, SSH keys, cloud credentials, service tokens, or build-argument secrets.

C3 validates that `docker.image` points to Docker Hub, uploads your workspace, and pulls the image on the GPU VM before running `bash /workspace/<script>` inside the container. Docker images must include `bash`; minimal images such as Alpine or distroless may not have it by default.

Docker images keep their own `PATH`, `HOME`, and other `ENV` defaults. C3 only injects the job-specific variables needed to find the workspace and artifact directory.

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

Use Bash-only mode by omitting both `python:` and `docker:`. C3 still runs your `script:` on the GPU VM, and that script can execute shell commands, compiled binaries, Julia, R, Rust, Fortran, MPI launchers, or anything else available in the workspace or installed by the script.

```
# .c3
project: bash-example
script: run.sh
gpu: l40
time: "00:30:00"

datasets:
  - ref: /datasets/example-inputs
    mount: /data

output:
  - ./results
```

```
# run.sh
#!/bin/bash
set -euo pipefail

mkdir -p results

# Any dependency setup done here runs again on every job.
chmod +x ./bin/my-simulation
./bin/my-simulation --input /data/input.dat --output results/output.dat
```

Bash-only jobs are the most flexible, but C3 does not cache an environment for them. If your script downloads packages, compiles dependencies, or creates virtual environments, that setup cost is paid on every run. Move that setup into `python.project` or `docker:` when repeat-job startup time matters.

## Script environment variables[​](#script-environment-variables "Direct link to Script environment variables")

Your script runs with a sanitized environment. C3 forwards the runtime variables needed by the GPU/software stack but does not expose agent credentials, storage keys, or arbitrary host secrets.

| Variable           | Description                                                                                                                          |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `C3_JOB_WORKDIR`   | Absolute path to the job workspace. Available in all modes                                                                           |
| `C3_ARTIFACTS_DIR` | Directory for files you want uploaded as job artifacts. Available in all modes                                                       |
| `PYTHONUNBUFFERED` | Set to `1` so Python output streams promptly. Available in all modes                                                                 |
| `TERM`             | Terminal type, defaulting to `dumb` if unset. Available in all modes                                                                 |
| `HOME`             | Set to the job workspace for Bash/Python jobs. In Docker jobs, the image controls `HOME`                                             |
| `PATH`             | Runtime `PATH` for Bash/Python jobs, with the Python virtualenv prepended when configured. In Docker jobs, the image controls `PATH` |
| `TMPDIR`           | Per-job temporary directory under the job workspace for Bash/Python jobs                                                             |
| `VIRTUAL_ENV`      | Set when `python.project` creates a virtualenv                                                                                       |

Write persistent results to `$C3_ARTIFACTS_DIR` or a path listed in `output:`; files outside the configured [artifact outputs](https://docs.cthree.cloud/artifacts.md) are discarded when the job ends. Use [data mounting](https://docs.cthree.cloud/data-mounting.md) for inputs that should live in C3 storage rather than in the project bundle.
