---
summary: "Create a bunker, run a command, read back a file, terminate it \u2014 five\
  \ minutes end-to-end."
title: Quickstart
path: quickstart
status: published
---

In five minutes you'll have a Linux microVM running on a ScaiBunker worker, you'll execute a command in it, drop a file into it, and tear it down.

You need:

- A ScaiGrid API key with `scaibunker:create` and `scaibunker:execute` (any tenant admin has these).
- The managed endpoint, or a self-hosted ScaiGrid with at least one worker registered.

```bash
export SCAIGRID_HOST="https://scaigrid.scailabs.ai"
export SCAIGRID_API_KEY="sgk_..."
```

## 1. Create the bunker

POST to `/bunkers` with the image, lifecycle mode, network profile, and resource request. Defaults are sensible for a one-off Python workload.

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image":"python-3.12","lifecycle_mode":"ephemeral","network_profile":"registry","cpu_millicores":1000,"memory_mb":512,"disk_mb":1024}'
```

```python
import httpx, os
HOST = os.environ["SCAIGRID_HOST"]
H = {"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"}

bunker = httpx.post(
    f"{HOST}/v1/modules/scaibunker/bunkers",
    headers=H,
    json={
        "image": "python-3.12",
        "lifecycle_mode": "ephemeral",
        "network_profile": "registry",
        "cpu_millicores": 1000,
        "memory_mb": 512,
        "disk_mb": 1024,
    },
).json()["data"]
print(bunker["id"], bunker["status"])
```

```javascript
const HOST = process.env.SCAIGRID_HOST;
const H = { "Authorization": `Bearer ${process.env.SCAIGRID_API_KEY}` };

const res = await fetch(`${HOST}/v1/modules/scaibunker/bunkers`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({
    image: "python-3.12",
    lifecycle_mode: "ephemeral",
    network_profile: "registry",
    cpu_millicores: 1000,
    memory_mb: 512,
    disk_mb: 1024,
  }),
});
const { data: bunker } = await res.json();
console.log(bunker.id, bunker.status);
```

Save the returned `bunker.id` — every subsequent call references it. Status starts at `pending` and flips to `running` once the worker has booted the microVM (usually under a second on a warm worker).

## 2. Run a command

Send a shell command to the bunker. The `registry` profile lets `pip` reach PyPI; the install takes a few seconds on a warm worker.

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers/$BUNKER_ID/exec" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"pip install --quiet pandas && python -c \"import pandas; print(pandas.__version__)\"","timeout_s":120}'
```

```python
result = httpx.post(
    f"{HOST}/v1/modules/scaibunker/bunkers/{bunker['id']}/exec",
    headers=H,
    json={
        "command": "pip install --quiet pandas && python -c 'import pandas; print(pandas.__version__)'",
        "timeout_s": 120,
    },
    timeout=180,
).json()["data"]
print(result["exit_code"], result["stdout"])
```

```javascript
const r = await fetch(`${HOST}/v1/modules/scaibunker/bunkers/${bunker.id}/exec`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({
    command: "pip install --quiet pandas && python -c 'import pandas; print(pandas.__version__)'",
    timeout_s: 120,
    working_dir: "/workspace",
  }),
});
const { data } = await r.json();
console.log(data.exit_code, data.stdout);
```

You should see the pandas version printed in `stdout`. The `registry` profile lets `pip` reach PyPI; on the `isolated` profile the install would fail with no route to host.

## 3. Write and read a file

PUT and GET against `/files/{path}` write and read bytes inline. The path is rooted at the bunker's filesystem; conventionally use `/workspace` for caller-supplied data.

```bash
printf "name,score\nalice,42\nbob,17\n" > scores.csv
curl -X PUT "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers/$BUNKER_ID/files/workspace/scores.csv" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" --data-binary @scores.csv

curl "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers/$BUNKER_ID/files/workspace/scores.csv" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

Files under `/workspace` are the convention for caller-supplied data.

## 4. Snapshot before terminating

To preserve state before tearing down, take a snapshot. Or pass `?snapshot=true` on terminate (step 5) to do snapshot-then-destroy in one call.

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers/$BUNKER_ID/snapshot" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

The snapshot id comes back on the bunker record; download the archive later with `GET /snapshots/{id}/archive`.

## 5. Terminate

DELETE on the bunker tears it down and decrements your tenant's quota counters across every bucket the bunker contributed to.

```bash
curl -X DELETE "$SCAIGRID_HOST/v1/modules/scaibunker/bunkers/$BUNKER_ID?snapshot=true" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

## What just happened

The scheduler picked one worker and booted a Firecracker microVM with the `python-3.12` ext4 rootfs, attached to a `registry`-profile network namespace. Every `exec` and file call streamed through ScaiGrid to the worker; large outputs go to S3 via the storage proxy. Quota counters in Redis went up on create and back down on terminate. Next: read [Architecture](./concepts/architecture), [Network profiles](./concepts/network-profiles), and the [data analysis tutorial](./tutorials/data-analysis-sandbox).
