# Persistent storage & object storage
URL: https://synquic.tech/docs/guides/persistent-storage.md

Keep the files your app writes. Pick the paths that survive a redeploy, or connect an S3 bucket so uploads live outside the container entirely.

import { Step, Steps } from 'fumadocs-ui/components/steps';

Every deploy replaces your app's container with a fresh copy. That's what makes deploys safe to repeat, and
it's also why a file your app wrote yesterday is gone today unless you tell Synq to keep it. This page covers
two ways to do that: **persistent storage** for paths on the server, and **object storage** for a bucket that
lives outside your app altogether.

<Callout title="Which one do I want?">
- **Uploads that users must never lose**: object storage (a bucket). It also keeps working when you run more
  than one copy of the app.
- **A cache, a SQLite file, a framework's own storage folder**: persistent storage (a path).
- **A named volume you want to see, size, back up and restore on its own**: the
  [Volumes manager](/docs/guides/volumes-backups).
- **A real database**: neither. Add a database service and connect it (see
  [Internal networking](/docs/guides/networking)).
</Callout>

## Where it is

Open the project and choose **Storage** in the project menu. For a single app it shows, from top to bottom:
**Volumes**, **Persistent storage**, **File mounts** and **Object storage**. The same cards (except Volumes)
are also on the **Configuration** tab.

For a compose or monorepo project, volumes are set per service. The **Storage** section shows the **Volumes**
manager and a **Volumes are set per service** card whose button opens **Apps & Services**; each service has
its own **Volumes** tab.

## Persistent storage (paths that survive a deploy)

Synq already knows what some frameworks write. A Laravel app keeps `storage/` with no configuration at all,
because that's where Laravel puts uploads, sessions, cache and its logs.

<Steps>

<Step>

### Look at the Persistent storage card

It lists the paths currently kept and says where the list came from: **Framework defaults for this stack.**
means Synq chose it, **Set for this project.** means you did, and **Nothing is kept between deploys.** means
the list is empty.

</Step>

<Step>

### Press Edit and list the paths, one per line

A plain path is relative to your app:

```text
storage
public/uploads
```

You can also give a full mount to choose the volume name, or to use a folder on the server:

```text
uploads:/app/public/uploads
/srv/app-data:/app/var
```

</Step>

<Step>

### Save, then deploy

Press **Save**. The message says **Storage updated. It applies on the next deploy.** The running container
keeps the mounts it started with.

</Step>

</Steps>

To go back to the framework's own list, press **Use framework defaults**. To keep nothing at all, save an
empty list: that's a real choice, and Synq won't quietly add the defaults back.

You can declare the same thing in your repository, so a fresh deploy of the same code gets it automatically:

```json title="synq.json"
{
  "volumes": ["storage", "uploads:/app/public/uploads"]
}
```

### Things worth knowing

- **The first deploy seeds the folder.** Whatever your app ships at that path (Laravel's `storage/`
  skeleton, for instance) is copied in, so the app doesn't start against an empty folder.
- **Later releases don't refresh it.** That's the point: it's your data now, not the image's. So don't list a
  path that also contains code. Laravel's `database/` folder holds migrations as well as the SQLite file,
  which is why Synq keeps `storage/` and not `database/`.
- **Volume names are scoped per project.** Two projects that both keep `storage` never share one volume.
  The named volumes these paths create also appear in the **Volumes** manager, where you can back them up. A
  folder on the server (`/srv/app-data:/app/var`) is a plain bind mount and isn't listed there.
- **One copy per server.** A path lives on one server's disk. Two copies of the app on different servers each
  see their own folder; use object storage for shared uploads.

## Object storage (an S3 bucket)

A path on the server stops data loss, but it doesn't scale: run two copies of your app and each one gets its
own uploads folder, which shows up as files that exist for some visitors and 404 for others. A bucket is
shared by every copy and outlives the server.

Synq connects a bucket using your framework's own variable names, so you don't have to hunt for them. A
Laravel app gets `FILESYSTEM_DISK=s3` plus the `AWS_*` variables its S3 disk reads; a Django app gets the
`AWS_STORAGE_BUCKET_NAME` / `AWS_S3_*` names django-storages reads; anything else gets neutral `S3_*` names.

<Steps>

<Step>

### Press Connect on the Object storage card

The card says **No bucket connected. Uploads stay on the container's disk.** until you do.

</Step>

<Step>

### Choose the source

- **An installed MinIO app**: if you installed MinIO from the **Apps** catalog, pick it under **Source**. Its
  endpoint and keys are filled in, your app joins its private network so the traffic never leaves the
  server, and the bucket MinIO created on install is prefilled.
- **External provider**: pick the **Provider** (Amazon S3, Cloudflare R2, Wasabi, Backblaze B2,
  DigitalOcean Spaces or Other S3-compatible) and fill in **Bucket**, **Endpoint** (not needed for Amazon S3),
  **Region**, **Access key ID** and **Secret access key**.

</Step>

<Step>

### Press Test & connect

Synq writes, reads and deletes one throwaway object before saving anything. If the bucket doesn't exist or
the keys are read-only, you find out here, not the first time a user uploads a file. Then: **Bucket verified
and connected. It applies on the next deploy.**

</Step>

</Steps>

The card then shows the bucket, the provider and exactly which **Variables** were set. **Disconnect** removes
those variables again: only the ones the connection added, never anything you set by hand.

<Callout type="info" title="Where the credentials live">
The keys are stored as encrypted, masked project variables, like all your secrets (see
[Environment variables](/docs/guides/environment-variables)). There's no second copy anywhere, and
disconnecting deletes them.
</Callout>

### Using the bucket from your code

Once connected, your framework's normal file API writes to the bucket: `Storage::put(...)` in Laravel,
`default_storage` in Django. For other stacks, read the `S3_*` variables the card lists and hand them to your
S3 client.

## Related guides

<Cards>
  <Card title="Volumes & volume backups" href="/docs/guides/volumes-backups" description="See, move, back up and restore named volumes." />
  <Card title="File mounts" href="/docs/guides/file-mounts" description="Mount a config file or certificate read-only." />
  <Card title="Backups & restore" href="/docs/guides/backups-restore" description="Back up databases and services on a schedule." />
</Cards>
