Guides

Persistent storage & object storage

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.

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.

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.
  • A real database: neither. Add a database service and connect it (see Internal networking).

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.

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.

Press Edit and list the paths, one per line

A plain path is relative to your app:

storage
public/uploads

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

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

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.

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:

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.

Press Connect on the Object storage card

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

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.

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.

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.

Where the credentials live

The keys are stored as encrypted, masked project variables, like all your secrets (see Environment variables). There's no second copy anywhere, and disconnecting deletes them.

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.

On this page