# C3 Docs

> C3 is a GPU compute marketplace for academics. Users configure projects with a .c3 YAML file and run c3 deploy to provision cloud GPUs, run jobs, and return results.

- [C3 Docs](https://docs.cthree.cloud/index.md)

## artifacts

- [Artifact Output](https://docs.cthree.cloud/artifacts.md): The output of a job is its artifacts. These are things like plots produced by your job, trained neural network weights, or saved checkpoints.

## dashboard

- [Web Dashboard](https://docs.cthree.cloud/dashboard.md): The C3 web dashboard at cthree.cloud/dashboard provides a visual interface for managing your GPU compute jobs, billing, and data.

## data-mounting

- [Data Mounting](https://docs.cthree.cloud/data-mounting.md): How C3 stores data

## environment

- [Environment](https://docs.cthree.cloud/environment.md): Every C3 job runs the script

## marketplace

- [Marketplace](https://docs.cthree.cloud/marketplace.md): C3 aggregates GPU capacity from multiple data centers. When you submit a job, we find available compute at competitive rates—no need to manage cloud accounts or hunt for capacity yourself.

## submission

- [Project Configuration](https://docs.cthree.cloud/submission.md): C3 projects are configured with a .c3 YAML file at the project root. Run c3 deploy from anywhere in the project to submit a job.

## warm-pool

- [Warm Pool](https://docs.cthree.cloud/warm-pool.md): C3's warm pool is the key to achieving a "mounted-like" GPU development experience. Instead of waiting minutes for a VM to provision, your code starts running in seconds.


---

# Full Documentation Content

# Artifact Output

The output of a job is its artifacts. These are things like plots produced by your job, trained neural network weights, or saved checkpoints.

Only files written to configured output directories are collected. By default this is `$C3_ARTIFACTS_DIR` (an `artifacts/` directory pre-created for every job). You can add more directories with the `output:` field in your `.c3` config. Files written elsewhere in the working directory are not collected.

## Configuring output[​](#configuring-output "Direct link to Configuring output")

Every job automatically collects files written to `$C3_ARTIFACTS_DIR`:

```
import os

output_dir = os.environ["C3_ARTIFACTS_DIR"]  # always set, pre-created
with open(os.path.join(output_dir, "metrics.json"), "w") as f:
    json.dump(results, f)
```

You can collect files from additional directories too:

```
output:
  - results
  - checkpoints
```

Both `$C3_ARTIFACTS_DIR` contents and `output:` directories end up in the same artifact manifest, downloadable with `c3 pull`.

## Storage quota[​](#storage-quota "Direct link to Storage quota")

Artifact bytes count toward your account's tracked storage usage. When the agent registers an artifact after upload, the control plane records the byte delta against your storage balance. This means your displayed storage usage may increase after artifact uploads.

The quota check is currently **soft-fail** — uploads are never rejected due to quota, but a warning is logged when usage exceeds the quota. You can check your current usage with `c3 balance`.

## Artifact lifecycle[​](#artifact-lifecycle "Direct link to Artifact lifecycle")

1. Your script writes files to `$C3_ARTIFACTS_DIR` and/or directories listed in `output:`
2. After the script finishes, the agent collects all output files and uploads them as content-addressed blobs
3. An artifact manifest is created at `/jobs/<jobId>/`
4. Artifact bytes are charged to your storage usage quota
5. You can download, browse, or reuse artifacts in another job

**Important:** If your script exits successfully (exit code 0) but artifact upload fails, the job is marked `FAILED` with reason `UPLOAD_ERROR`. This is non-retryable — the GPU work completed, but the output could not be saved. Check your storage configuration and job logs if you encounter this status. When the script itself fails (non-zero exit), is canceled, or times out, artifact upload is best-effort: upload errors are logged but the job status reflects the script failure, cancellation, or timeout.

## Browsing artifacts[​](#browsing-artifacts "Direct link to Browsing artifacts")

Artifacts are stored the same way as uploaded datasets, using the same content-addressed storage. You can browse them with the same `c3 data` commands, just at a different path.

You can reach a job's artifacts in two ways:

```
c3 data ls /jobs/job_abc123/                              # By job ID (resolves project automatically)
c3 data ls /projects/my-project/jobs/job_abc123/          # By project + job ID
```

Both paths point to the same data. Having two paths does not mean the data is stored twice.

To list all jobs with artifacts in a project:

```
c3 data ls /projects/my-project/jobs/
```

## Downloading artifacts[​](#downloading-artifacts "Direct link to Downloading artifacts")

Download artifacts to your local machine with `c3 pull`:

```
c3 pull job_abc123          # Download a specific job's artifacts
c3 pull                     # Download all new completed jobs
```

Or use `c3 data cp` for more control:

```
c3 data cp /jobs/job_abc123/ ./local-output/
```

## Reusing artifacts in another job[​](#reusing-artifacts-in-another-job "Direct link to Reusing artifacts in another job")

Because artifacts live in C3's centralised storage, you can mount them directly into a new job without downloading to your local machine first. This avoids the slow local-machine bottleneck entirely:

```
datasets:
  - ref: /jobs/job_abc123
    mount: /data/prev_weights
```

This mounts the output from `job_abc123` at `/data/prev_weights` in your new job. The data transfers server-to-server at full speed.

## Job chaining[​](#job-chaining "Direct link to Job chaining")

Job chaining is mounting a previous job's artifacts directly into a new job. This lets you build multi-stage pipelines where each stage feeds into the next, with all data staying in C3's storage network:

```
# Stage 1: Preprocess data
c3 deploy -f
# Job completes, producing job_preprocess

# Stage 2: Train model (mount preprocessed data)
# Update .c3:
#   datasets:
#     - ref: /jobs/job_preprocess
#       mount: /data/preprocessed
c3 deploy -f
# Job completes, producing job_train

# Stage 3: Evaluate (mount trained model)
# Update .c3:
#   datasets:
#     - ref: /jobs/job_train
#       mount: /data/model
c3 deploy -f
```

Each stage mounts the previous stage's output directly. Data stays in C3's storage network and never needs to touch your local machine.


---

# Web Dashboard

The C3 web dashboard at [cthree.cloud/dashboard](https://cthree.cloud/dashboard) provides a visual interface for managing your GPU compute jobs, billing, and data.

**Everything available in the dashboard is also available via the CLI.** The CLI is the primary interface for C3 — the dashboard is a convenience layer that mirrors CLI functionality.

## Dashboard Pages[​](#dashboard-pages "Direct link to Dashboard Pages")

| Dashboard                                                   | CLI Equivalent            | Description                                                                             |
| ----------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------- |
| [Jobs](https://cthree.cloud/dashboard/squeue)               | `c3 squeue`               | View all jobs with status, GPU, runtime. Click any job to see details.                  |
| [Billing](https://cthree.cloud/dashboard/billing)           | `c3 balance` / `c3 topup` | View credit balance, transaction history, and top up via Stripe.                        |
| [Subscription](https://cthree.cloud/dashboard/subscription) | `c3 upgrade`              | View current plan, upgrade/downgrade between tiers, cancel or resume your subscription. |
| [Data](https://cthree.cloud/dashboard/data)                 | `c3 data ls`              | Browse your datasets and storage usage.                                                 |
| [Settings](https://cthree.cloud/dashboard/settings)         | `c3 apikey`               | Create and manage API keys for programmatic access.                                     |

## Login[​](#login "Direct link to Login")

The dashboard uses the same Auth0 authentication as the CLI. Click "Login" in the header at cthree.cloud, or navigate directly to `/dashboard` to be prompted to sign in.

Once logged in, the dashboard uses the same API and credentials as `c3 login` — you'll see the same jobs, balance, and data in both.

## Key Differences from CLI[​](#key-differences-from-cli "Direct link to Key Differences from CLI")

* **No job submission**: Jobs are submitted via `c3 deploy` from the CLI. The dashboard shows and manages existing jobs.
* **No data upload/download**: Data transfers use `c3 data cp`. The dashboard browses data that's already uploaded.
* **Stripe redirect**: Topping up with credits, or subscribing to a paid plan from free, opens the same Stripe checkout page the CLI opens. Tier switches (pro ↔ max), downgrades, and cancellations happen inline without redirecting to Stripe.


---

# Data Mounting

## How C3 stores data[​](#how-c3-stores-data "Direct link to How C3 stores data")

C3 keeps all data (datasets you upload and artifacts your jobs produce) on a centralised storage server with high-bandwidth connections to GPU nodes around the world. Think of it like a warehouse on a motorway network: the links between the warehouse and the GPUs are fast, high-bandwidth connections. Uploading from your local machine is the bottleneck, since home and office network connections to remote servers are much slower than server-to-server transfers. The good news is you only need to upload once. After that, C3 moves data between its storage and GPUs at full speed.

## Paths[​](#paths "Direct link to Paths")

Data in C3 can be organised by project, by job, or both:

| Path                                | What it contains                  |
| ----------------------------------- | --------------------------------- |
| `/datasets/{name}/`                 | Uploaded datasets                 |
| `/jobs/{jobId}/`                    | Job output artifacts              |
| `/projects/{project}/data/{name}/`  | Datasets scoped to a project      |
| `/projects/{project}/jobs/{jobId}/` | Job artifacts scoped to a project |

You can use whichever path style suits your workflow. `/jobs/{jobId}/` resolves the project automatically.

## Upload a dataset[​](#upload-a-dataset "Direct link to Upload a dataset")

```
c3 data cp ./local-data/ /datasets/my-dataset/
```

This uploads your data to C3's centralised storage. You only need to do this once. After the initial upload, every `c3 deploy` that references this dataset gets rapid access to it directly from the storage network, with no re-upload needed.

C3 uses content-addressed deduplication: each file is hashed (SHA256) before upload, and if the content already exists, the upload is skipped. This means re-uploading a dataset with minor changes only transfers the files that actually changed, and overall storage usage can be lower than standard methods since identical files are never stored twice (see [How deduplication works](#how-deduplication-works) below).

## Browse data[​](#browse-data "Direct link to Browse data")

Use `c3 data ls` to browse datasets, versions, and files:

```
c3 data ls /datasets/                          # List all datasets
c3 data ls /datasets/my-dataset/               # List versions
c3 data ls -l /datasets/my-dataset/@latest/     # List files in latest version
```

## Mount a dataset in a job[​](#mount-a-dataset-in-a-job "Direct link to Mount a dataset in a job")

Reference the dataset in your `.c3` config:

```
datasets:
  - ref: /datasets/my-dataset
    mount: /data/my-dataset
```

Once referenced, C3 handles moving the data to whichever GPU your job lands on. From your script's perspective, the files are simply local at the mount path. You read them like any other files:

```
import numpy as np

data = np.loadtxt("/data/my-dataset/measurements.csv", delimiter=",")
```

### Mount path rules[​](#mount-path-rules "Direct link to Mount path rules")

* Mount paths must be **absolute** (start with `/`). Relative paths are rejected at submission time with a clear error.
* If `mount` is omitted, it is auto-derived as `/data/<dataset-name>` (e.g., `/datasets/cifar10` becomes `/data/cifar10`).
* In `.c3` YAML, a relative mount like `mydata` is auto-prefixed to `/data/mydata`.

### Local directories[​](#local-directories "Direct link to Local directories")

You can reference a local directory as a dataset. C3 auto-uploads it before submitting the job:

```
datasets:
  - ref: ./local-data
    mount: /data/train
```

This is equivalent to running `c3 data cp ./local-data/ /datasets/...` yourself, but handled automatically.

## Versioning[​](#versioning "Direct link to Versioning")

Every upload creates a new version. Your jobs always get exactly the data they expect:

```
c3 data log /datasets/my-dataset/
```

```
VERSION   CREATED              FILES   SIZE
v3        2024-01-15 10:00:00  1000    2.5GB
v2        2024-01-10 09:00:00  1000    2.4GB
v1        2024-01-05 08:00:00  500     1.2GB
```

Jobs reference the latest version by default, or you can pin to a specific version for reproducibility.

## How deduplication works[​](#how-deduplication-works "Direct link to How deduplication works")

All data in C3 (datasets, workspaces, and job artifacts) uses the same content-addressed storage. Every file is stored as a **blob** keyed by its SHA256 hash, and a **manifest** lists which blobs make up each dataset, workspace, or set of job artifacts.

This means:

* **Cross-job dedup**: If two jobs produce identical output files, the data is stored once
* **Workspace dedup**: Re-deploying the same code skips uploading unchanged files
* **Cross-dataset dedup**: Identical files shared across datasets use the same storage
* **Instant re-uploads**: `c3 data cp` only uploads files that have actually changed

Deduplication is automatic and transparent. Artifacts still appear per-job (each job has its own listing), but identical files across jobs share storage behind the scenes.

## Data commands[​](#data-commands "Direct link to Data commands")

| Command                | Description                                   |
| ---------------------- | --------------------------------------------- |
| `c3 data ls /path/`    | List files, datasets, or job artifacts        |
| `c3 data cp SRC DST`   | Copy files (upload or download)               |
| `c3 data rm -r /path/` | Delete a dataset (requires `-r` for datasets) |
| `c3 data du /path/`    | Show disk usage                               |
| `c3 data log /path/`   | Show version history                          |


---

# 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.


---

# Marketplace

C3 aggregates GPU capacity from multiple data centers. When you submit a job, we find available compute at competitive rates—no need to manage cloud accounts or hunt for capacity yourself.

## Available GPUs[​](#available-gpus "Direct link to Available GPUs")

| GPU       | VRAM | Best for                                                                     |
| --------- | ---- | ---------------------------------------------------------------------------- |
| L40       | 48GB | Recommended for new jobs, training, inference, general-purpose GPU workloads |
| H100 80GB | 80GB | Temporarily unavailable for non-admin job submissions                        |
| A100 80GB | 80GB | Temporarily unavailable for non-admin job submissions                        |
| RTX A4000 | 16GB | Light training, testing, development                                         |

Set `gpu: l40` in your `.c3` config for new jobs. Run `c3 list` to see current pricing and availability.

## Pricing[​](#pricing "Direct link to Pricing")

### Compute[​](#compute "Direct link to Compute")

You're billed per second of actual compute time—not for time spent queuing or provisioning. New accounts receive £10 in free credits. Check your balance:

```
c3 balance
```

### Plans[​](#plans "Direct link to Plans")

Plans are about **iteration speed**, not storage. The warm pool plus smart caching turns 5-45 minute cold starts into \~20-second job starts. Every new account starts with a **14-day Pro trial** — warm-pool access, smart caching, 100 GB of storage, and £10 in compute credits.

| Plan | Price     | Warm pool          | Smart caching | Concurrent jobs | Walltime  | Storage |
| ---- | --------- | ------------------ | ------------- | --------------- | --------- | ------- |
| Free | £0/month  | — (cold pool only) | —             | 1               | 6 h cap   | 10 GB   |
| Pro  | £25/month | L40 (\~20s start)  | yes           | 5               | 24 h      | 100 GB  |
| Max  | £45/month | L40 (\~20s start)  | yes           | 20              | unlimited | 1 TB    |

Manage your plan anytime:

```
c3 upgrade              # Interactive picker
c3 upgrade pro          # Subscribe to or switch to Pro
c3 upgrade max          # Subscribe to or switch to Max
c3 upgrade free         # Cancel subscription (takes effect at period end)
c3 upgrade free --yes   # Non-interactive cancel (for scripts)
c3 upgrade free -y      # Short form of --yes
```

Upgrades (e.g. Pro → Max) are immediate: you get the new features right away and your card is charged the prorated difference for the remainder of the current billing period. Downgrades (e.g. Max → Pro) and cancellations take effect at the **end of your current billing period** — you keep the features you paid for until then. To reverse a pending downgrade or cancellation before it takes effect, run `c3 upgrade <current-tier>`.

When your trial ends, you're moved to the Free plan. Existing data is preserved — you just can't upload more until you upgrade or free up space.

## How jobs run[​](#how-jobs-run "Direct link to How jobs run")

1. **PENDING** — Job submitted, waiting for a GPU
2. **SCHEDULING** — Assigning to a machine
3. **RUNNING** — Your script is executing
4. **COMPLETED** — Done. Download results with `c3 pull`
5. **SYNCED** — Results downloaded locally

Failed or cancelled jobs show as **FAILED**, **CANCELED**, or **TIMED\_OUT**.

## Warm pool[​](#warm-pool "Direct link to Warm pool")

C3 maintains a pool of pre-provisioned GPUs. Jobs hitting the warm pool start running in **\~20 seconds** instead of waiting 5-45 minutes for cold provisioning.

| Path           | Allocation   | Total Startup |
| -------------- | ------------ | ------------- |
| **Warm pool**  | seconds      | \~20 seconds  |
| Cold provision | 5-45 minutes | 5-45 minutes  |

This enables rapid iteration: edit locally, submit, see results quickly, repeat.

**[Learn more about the warm pool →](https://docs.cthree.cloud/warm-pool.md)**

## Providers[​](#providers "Direct link to Providers")

Jobs currently run on **Hyperstack**. We're onboarding additional providers to increase capacity and reduce prices.


---

# Project Configuration

C3 projects are configured with a `.c3` YAML file at the project root. Run `c3 deploy` from anywhere in the project to submit a job.

Key differences from running locally

**Environment:** Every job runs a Bash script. Use `python:` or `docker:` when you want C3 to prepare the environment; omit both for Bash-only jobs where setup happens inside the script each time. See [Environment](https://docs.cthree.cloud/environment.md).

**Data mounting:** Your data lives in C3's centralised storage. You tell C3 which datasets to mount and where using `datasets:`. Your script reads from that path as if the files were local. See [Data Mounting](https://docs.cthree.cloud/data-mounting.md).

**Artifact output:** Files on the GPU are discarded when the job ends. Write results to `$C3_ARTIFACTS_DIR` or a directory listed in `output:`. Only these are collected. See [Artifact Output](https://docs.cthree.cloud/artifacts.md).

## Configuration reference[​](#configuration-reference "Direct link to Configuration reference")

| Field            | Type   | Description                                                                                                                   |
| ---------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `project`        | string | Project name (auto-generated if not set). Lowercase alphanumeric + hyphens.                                                   |
| `script`         | string | **Required.** Path to a bash script with your execution commands (e.g. `run.sh`), relative to `.c3`                           |
| `gpu`            | string | GPU profile (use `l40` for new jobs; A100/H100 profiles are temporarily unavailable for non-admin submissions). See `c3 list` |
| `time`           | string | Maximum runtime in `HH:MM:SS` format. You're only charged for actual usage                                                    |
| `job_name`       | string | Job display name                                                                                                              |
| `python.project` | string | Path to Python project dir with `pyproject.toml` + `uv.lock`. Mutually exclusive with `docker:`                               |
| `docker.image`   | string | Public Docker Hub image reference to pull on the GPU VM. Mutually exclusive with `python:`                                    |
| `datasets`       | list   | Datasets to mount (`ref` + optional `mount`). See [Data Mounting](https://docs.cthree.cloud/data-mounting.md)                 |
| `output`         | list   | Directories to collect as artifacts. See [Artifact Output](https://docs.cthree.cloud/artifacts.md)                            |

## Full example[​](#full-example "Direct link to Full example")

All job configuration goes in `.c3`. The `script` field points to a bash script containing your execution commands — no `#SBATCH` or `#C3` directives needed.

```
# .c3
project: my-experiment
script: run.sh
gpu: l40
time: "04:00:00"
job_name: train-model

python:
  project: ./

datasets:
  - ref: /datasets/imagenet
    mount: /data/imagenet

output:
  - ./checkpoints
  - ./results
```

```
# run.sh
#!/bin/bash
python3 train.py
```

See [Environment](https://docs.cthree.cloud/environment.md) for Python, Docker, and Bash-only examples.

## Creating a new project[​](#creating-a-new-project "Direct link to Creating a new project")

```
cd my-project
c3 init
```

This creates a `.c3` file with sensible defaults. Edit it and deploy with `c3 deploy`.

## API keys[​](#api-keys "Direct link to API keys")

For team billing or CI/CD, add an API key to `.c3.local` (keep this file out of version control):

```
# .c3.local — secrets (add to .gitignore)
api_key: c3_key_abc123def456...
```

Create keys with `c3 apikey create my-team-key`. API keys are scoped to the org that created them, and deploy uploads are stored under that same org. Create and use the key from the org that should own the project and job.

The API key takes priority over `c3 login` credentials. You can also set it with:

```
export C3_API_KEY=c3_key_abc123def456...
```

Use `c3 whoami` to verify the active credential and org before deploying. If a key was pasted into a shared shell, CI log, or support thread, revoke it with `c3 apikey revoke <key-id>` and create a replacement.


---

# Warm Pool

C3's warm pool is the key to achieving a **"mounted-like" GPU development experience**. Instead of waiting minutes for a VM to provision, your code starts running in seconds.

## The Problem with Cold Provisioning[​](#the-problem-with-cold-provisioning "Direct link to The Problem with Cold Provisioning")

Traditional cloud GPU workflows require spinning up a fresh VM for every job:

1. **Request VM** from cloud provider
2. **Wait for allocation** (1-10 min)
3. **Boot the VM** (1-5 min)
4. **Initialize GPU drivers** (1-10 min)
5. **Download your code** (seconds to minutes)
6. **Finally run your job**

This adds up to **5-45 minutes of waiting** before your code even starts. For iterative development—tuning hyperparameters, debugging training loops, testing model changes—this latency kills productivity.

## How the Warm Pool Works[​](#how-the-warm-pool-works "Direct link to How the Warm Pool Works")

C3 maintains a fleet of **pre-provisioned GPUs**. These VMs are:

* Already booted and initialized
* GPU drivers loaded and ready
* C3 agent running and polling for work
* Network configured with fast access to our control plane

When you submit a job, the scheduler finds an idle warm GPU and assigns it inline—**allocation takes under a second**, with total startup of \~20 seconds (agent heartbeat + code download):

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           C3 WARM POOL ARCHITECTURE                         │
└─────────────────────────────────────────────────────────────────────────────┘

                                    ┌─────────────┐
                                    │   Your Job  │
                                    │  c3 deploy  │
                                    └──────┬──────┘
                                           │
                                           ▼
┌──────────┐                      ┌─────────────────┐
│          │    Job submitted     │                 │
│   You    │ ──────────────────▶  │  C3 Control     │
│          │   allocation < 1s    │  Plane          │
└──────────┘                      └────────┬────────┘
                                           │
                                           ▼
                              ┌──────────────────────┐
                              │   GPU PROVIDER(S)    │
                              │  ┌────┐ ┌────┐       │
                              │  │GPU │ │GPU │  ...  │
                              │  │ ✓  │ │ ✓  │       │
                              │  └────┘ └────┘       │
                              │    Warm Pool         │
                              └──────────┬───────────┘
                                         │
                                         ▼
                              ┌───────────────────┐
                              │  Job runs on      │
                              │  first available  │
                              │  warm GPU         │
                              └───────────────────┘
```

### Warm Path vs Cold Path[​](#warm-path-vs-cold-path "Direct link to Warm Path vs Cold Path")

| Metric              | Warm Pool    | Cold Provision                  |
| ------------------- | ------------ | ------------------------------- |
| **Allocation time** | < 1 second   | 5-45 minutes                    |
| **Total startup**   | \~20 seconds | 5-45 minutes                    |
| **When used**       | Most jobs    | Pool exhausted / rare GPU types |

## Sub-Second Allocation[​](#sub-second-allocation "Direct link to Sub-Second Allocation")

When a warm GPU is available, job allocation is **nearly instantaneous**:

1. **You submit** → Job hits the C3 control plane
2. **Sub-second allocation** → Scheduler finds an idle warm GPU and assigns your job
3. **Agent pickup (\~5s)** → Agent picks up the job on its next heartbeat
4. **Code download** → Bundle is fetched and extracted
5. **Your code runs** → Execution begins, logs stream back immediately

This transforms GPU development from a **batch workflow** into an **interactive one**. It feels like having a GPU mounted to your local machine.

## Pool Scaling[​](#pool-scaling "Direct link to Pool Scaling")

The warm pool scales with demand. C3 uses a baseline target per GPU profile plus demand-based scaling:

* **Low demand**: Pool holds a baseline number of warm GPUs
* **High demand**: Pool scales up to meet job volume (with a configurable cap)
* **Burst load**: Overflow goes to cold provisioning for currently available profiles (still works, just slower)

## When Cold Provisioning Happens[​](#when-cold-provisioning-happens "Direct link to When Cold Provisioning Happens")

Sometimes jobs use cold provisioning instead:

* **Pool exhausted**: All warm GPUs are busy during high demand
* **Rare GPU type**: Specialized hardware not kept warm
* **Unusual demand patterns**: Spikes beyond the baseline pool capacity

For currently available GPU profiles, cold provisioning still works—your job will run, it just takes longer to start. C3 automatically falls back to cold provisioning when needed.

## Best Practices[​](#best-practices "Direct link to Best Practices")

### Maximize Warm Pool Benefits[​](#maximize-warm-pool-benefits "Direct link to Maximize Warm Pool Benefits")

1. **Match your plan to your GPU** — Pro and Max keep L40 warm; Free uses cold provisioning only.
2. **Keep jobs small** — Finish faster, return GPU to pool for others
3. **Use `--follow`** — See real-time logs as your job runs
4. **Submit during active hours** — Pool is warmest when others are using it too

### Understand the Timing[​](#understand-the-timing "Direct link to Understand the Timing")

* **Allocation time**: How long from submission until a GPU is assigned (seconds for warm, minutes for cold)
* **Total startup**: Time from submission until your code starts running (\~20 seconds for warm, minutes for cold)
* **Runtime**: Your actual code execution
* **Total time**: Everything from submit to completion

## The Development Experience[​](#the-development-experience "Direct link to The Development Experience")

With the warm pool, your workflow becomes:

```
┌─────────────────────────────────────────────────────────────────┐
│                    ITERATIVE GPU DEVELOPMENT                    │
└─────────────────────────────────────────────────────────────────┘

    ┌──────────┐      ┌──────────┐      ┌──────────┐
    │  Edit    │      │  Submit  │      │   See    │
    │  Code    │ ───▶ │   Job    │ ───▶ │ Results  │
    │ Locally  │      │ c3 deploy│      │  quickly │
    └──────────┘      └──────────┘      └──────────┘
          ▲                                   │
          │                                   │
          └───────────────────────────────────┘
                    Iterate rapidly
```

This is especially powerful for:

* **ML experimentation** — Try different hyperparameters quickly
* **Debugging** — Add print statements, see output fast
* **Prototyping** — Test ideas without provisioning overhead
* **Education** — Learn GPU programming interactively

The warm pool makes cloud GPUs feel local.


---

