> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rigbox.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Persistent Volumes

> Durable ext4 storage that survives redeploys and stop→start — the right home for SQLite databases, uploads, and logs.

A persistent volume is an ext4 drive attached to a workspace as a **second disk**, separate from the workspace's root filesystem. Because it lives outside the directory that `rig deploy` rsyncs, anything you write there survives redeploys, restarts, and stop→start cycles.

## Why you need one

`rig deploy` **wipes and re-rsyncs the synced app directory on every deploy**. That makes deploys clean and reproducible, but it means you must never store durable data inside the app dir — a redeploy would erase it.

Durable state belongs on a persistent volume. The usual convention is a volume named `data` mounted at `/home/developer/data`, exposed to the app as `DATA_DIR`:

```yaml theme={null}
env:
  DATA_DIR: /home/developer/data
```

Store SQLite databases, uploaded files, and logs under `$DATA_DIR` and they stay put across deploys.

<Note>
  A persistent volume survives **redeploys** (the rsync wipe), **app restarts**, and **stop→start** of the workspace. It is destroyed only when you delete the volume or the workspace itself.
</Note>

## Declare a volume in rig.yaml

The canonical path is to declare durable disks once under `workspace.volumes`, then opt each app into the mount with `volumes: [data]`. The volume is created or reattached on deploy, and only apps that reference it receive the mount.

```yaml theme={null}
name: markdown-notes
port: 8080
workspace:
  volumes:
    - name: data
      mountPath: /home/developer/data
      sizeMb: 1024
install: pip install --break-system-packages flask gunicorn
start: gunicorn --bind 0.0.0.0:8080 app:app
health: { path: /healthz, timeoutSeconds: 30 }
volumes: [data]
env:
  DATA_DIR: /home/developer/data
```

Point `DATA_DIR` at the same path and your app reads and writes durable state there.

For multi-app projects, keep the declaration at the workspace level and attach it only to the apps that need it:

```yaml theme={null}
workspace:
  image: base
  volumes:
    - name: data
      mountPath: /home/developer/data
      sizeMb: 1024

apps:
  api:
    path: ./api
    port: 5100
    start: npm start
    volumes: [data]
    env:
      DATA_DIR: /home/developer/data

  web:
    path: ./web
    port: 5101
    start: npm start
    dependsOn: [api]
    visibility: public
```

App references can be written as `volumes: [data]` or `volumes: [{ name: data }]`. App-local full specs are still supported for single-app or one-off cases:

```yaml theme={null}
volumes:
  - name: data
    mountPath: /home/developer/data
    sizeMb: 1024
```

## Manage volumes with the CLI

You can also create and manage volumes imperatively, independent of a deploy:

```bash theme={null}
# create a volume (size in MiB, mounted at an absolute path)
rig volume create --workspace <WORKSPACE_NAME> --name data --size-mb 1024 --mount-path /home/developer/data
rig volume ls --workspace <WORKSPACE_NAME>                       # list volumes for a workspace
rig volume resize --workspace <WORKSPACE_NAME> --volume <VOLUME_ID> --size-mb 5120   # grow a volume
rig volume rm --workspace <WORKSPACE_NAME> --volume <VOLUME_ID>  # delete a volume (destroys its data)
```

<Note>
  `rig volume resize` and `rig volume rm` take the volume id (from `rig volume ls`), not its name.
</Note>

Each command targets a specific workspace with `--workspace`:

```bash theme={null}
# List volumes for a workspace
rig volume ls --workspace <WORKSPACE_NAME>

# Create a volume (size in MiB, with an absolute mount path)
rig volume create --workspace <WORKSPACE_NAME> \
  --name data \
  --size-mb 1024 \
  --mount-path /home/developer/data
```

<Warning>
  Deleting a volume (`rig volume rm`) permanently destroys the data on it. This is separate from deleting the workspace — but deleting the workspace destroys its volumes too.
</Warning>

## The SQLite-under-\$DATA\_DIR pattern

The most common use is a SQLite database that outlives deploys. Two pieces make it work:

1. Set `DATA_DIR=/home/developer/data` in `env:` and put the database file under it.
2. `mkdir -p "$DATA_DIR"` at startup — a fresh workspace may not have the directory yet.

```python theme={null}
import os, sqlite3

DATA_DIR = os.environ.get("DATA_DIR", "/home/developer/data")
os.makedirs(DATA_DIR, exist_ok=True)        # the dir may not exist on a fresh workspace
db = sqlite3.connect(os.path.join(DATA_DIR, "notes.db"))
```

Because `notes.db` lives on the volume, not in the synced app directory, a `rig deploy` rsyncs your new code over the app dir while the database file is left untouched.

<Tip>
  To verify persistence, deploy the app, create some data, run `rig deploy` again, and confirm the data is still there. The rsync wipe replaces your code; `$DATA_DIR` survives.
</Tip>

## Next steps

* [Deploying with rig deploy](/guides/deploying) — the full deploy workflow and the rsync model.
* [Releases & Rollback](/guides/releases-and-rollback) — roll the environment back without touching volume data.
* [Snapshots](/guides/snapshots) — capture the *entire* disk (including the volume) for checkpoint and recovery.
