# Storage

> Keep blobs on the local filesystem or in an S3-compatible bucket such as Cloudflare R2 or SeaweedFS.

Source: https://docs.telesphorepo.com/operations/storage/

Telesphorepo keeps metadata in SQLite and every file (artifact, metadata and index) as a blob in one storage backend. There are two backends:

- `fs` stores blobs on a local directory.
- `s3` stores blobs in any S3-compatible bucket: Cloudflare R2, SeaweedFS, AWS S3 and similar. The value `r2` is an alias for `s3`.

The backend is the same for all repos. Blob keys look the same on both backends: `repos/{repo}/{path}`. On S3, the optional `s3.prefix` goes in front.

You set the backend on the **Settings** page, group **Storage**. Changes apply without a restart.

## Filesystem

`fs` is the default. Blobs go in `blobs/` inside the data directory (`/data/blobs` in the container image). Set `fs.root` to use another directory.

> Tip
>
> In the container, mount a volume on `/data`. The database, the upload spool, the master key and the blobs all live there. See [Backups and data](/operations/backups-and-data/).

## S3-compatible

Set these keys:

| Setting                | Meaning                                                           |
| ---------------------- | ----------------------------------------------------------------- |
| `storage`              | `s3` (or `r2`)                                                    |
| `s3.endpoint`          | Full URL of the S3 API                                            |
| `s3.region`            | Region name. Default `auto`                                       |
| `s3.bucket`            | Bucket name. The bucket must already exist                        |
| `s3.access_key_id`     | Access key ID                                                     |
| `s3.secret_access_key` | Secret key. The server stores it encrypted and never returns it   |
| `s3.prefix`            | Optional prefix for every object key, for example `telesphorepo/` |
| `s3.path_style`        | Path-style addressing. Default `false`                            |
| `s3.redirect`          | Redirect downloads to a presigned URL. Default `true`             |
| `presign_ttl`          | Lifetime of a presigned URL. Default `5m`                         |

- Cloudflare R2

  | Setting         | Value                                        |
  | --------------- | -------------------------------------------- |
  | `s3.endpoint`   | `https://<account>.r2.cloudflarestorage.com` |
  | `s3.region`     | `auto`                                       |
  | `s3.bucket`     | your bucket                                  |
  | `s3.path_style` | `false`                                      |

  Create an R2 API token with read and write access to the bucket. Use its access key ID and secret.

- SeaweedFS

  | Setting         | Value                             |
  | --------------- | --------------------------------- |
  | `s3.endpoint`   | `http://seaweedfs:8333`           |
  | `s3.region`     | any name, for example `us-east-1` |
  | `s3.bucket`     | your bucket                       |
  | `s3.path_style` | `true`                            |

  SeaweedFS and MinIO need path-style addressing. R2 and AWS do not.

You can also set any of these keys from the environment. The variable is `TR_` plus the key in upper case, with dots as underscores: `TR_S3_BUCKET`, `TR_S3_ACCESS_KEY_ID`, `TR_S3_SECRET_ACCESS_KEY`. A setting that comes from the environment is read-only in the UI. With `TR_STORAGE=s3`, the server refuses to start unless the bucket, endpoint and keys are set.

### The connection test

Before the server saves a change to a backend key, it runs a probe. The probe writes, reads, checks and deletes one object named `_telesphorepo/probe-<hex>`. If the probe fails, the server saves nothing and shows the error. The credentials need permission for all four actions.

You can also run the probe without saving. `POST /api/settings/storage/test` takes the same body as the settings change. See the [HTTP API](/reference/http-api/).

## Download redirects

With `s3.redirect` on, a `GET` for a Maven or raw file on S3 answers `302` with a presigned URL. The client downloads the file straight from the bucket. The server still checks the caller’s permission first.

- `HEAD` is never redirected.
- A presigned URL works for anyone who has it until `presign_ttl` ends.
- Clients and build tools must reach the bucket endpoint. If the bucket is on a private network, turn `s3.redirect` off. The server then streams every download.

## Change the backend

If your server has files and you point it at another location, the server moves the existing blobs for you. A change of only credentials, region, path style or redirect does not move anything. It saves and swaps at once. See [Moving storage](/operations/moving-storage/).

> Caution
>
> A storage key that the environment sets is read-only in the UI. The server answers `409` if a request tries to change it. To start a move from the UI, remove the `TR_` variable first and restart.
