# Data ownership
URL: https://synquic.tech/docs/architecture/data-ownership.md

Where your data lives when you self-host, how to export and import everything so there's no lock-in, and how Synq guards an embedded data directory.

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

Synq is explicit about where every piece of data lives, because that's what makes self-hosting
trustworthy. When you run Synq yourself, your data sits in a database **on your own machine or
server** — and you can pack all of it into a single file and carry it somewhere else whenever you like.
Think of it like a filing cabinet you own: the folder is yours, the key is yours, and you can move the
whole cabinet without asking anyone.

## Where your data lives (self-hosted)

Your projects, and all of their deployments, domains, environment variables, and logs, live
only in your instance's database. Nothing about them is sent anywhere else: Synq's hosted cloud is switched
off, so every install is completely standalone.

Synq picks the database based on what you configure:

| Mode | When it's used | Where the data sits |
|---|---|---|
| **PGlite (embedded)** | No `DATABASE_URL` and no `POSTGRES_*` set: the zero-config default | A folder on disk (a full PostgreSQL cluster, no separate server process) |
| **PostgreSQL server** | You set `DATABASE_URL`, or the `POSTGRES_*` / `PG*` variables — typical for a Docker Compose self-host | Your Postgres server (Synq connects with a normal connection pool) |

**PGlite** is PostgreSQL compiled to run *inside* the Synq process — same SQL, same tables, but no
database server to install or babysit. Every repository and service in Synq talks to a single unified
database type, so it never knows or cares which of the two is running underneath.

<Callout title="Where the embedded database lives on disk">
By default PGlite stores its data in **`~/.synq/data`** (outside your project folder, so it's never
committed to git). Set **`PGLITE_DATA_DIR`** to put it anywhere else. On a Docker Compose self-host you'll
usually point Synq at a real Postgres with `DATABASE_URL` (or `POSTGRES_HOST` / `POSTGRES_PASSWORD`)
instead. See [Installation](/docs/getting-started/installation) for the full set of options.
</Callout>

## No lock-in: export and import everything

Because a self-hosted instance owns its data, Synq lets an **owner** dump the *entire* instance to one
JSON file and load it into another install. That's how you move between machines, back up before a big
change, or hand a project set to a teammate — no proprietary format, no export ticket, no waiting.

You can do it from any of three surfaces:

| Surface | How |
|---|---|
| **Dashboard** | **Settings → Instance**, the **Export** and **Import** cards (self-hosted, owner only) |
| **CLI** | `synq system data-transfer export` and `synq system data-transfer import` |
| **API** | `POST /api/system/data-transfer/export` and `POST /api/system/data-transfer/import` |

### What's in the file

The export contains every table in your instance — users, projects, deployments, domains, servers,
settings, and so on. Secrets (environment variable values, access tokens, SSH credentials, backup-destination
keys, webhook secrets) are stored *encrypted* in your database at rest. During export they are **never left
as raw ciphertext in the file**: Synq lifts each secret out and re-seals the whole set in a separate
bundle locked with a **passphrase you choose**. The passphrase is run through a slow, memory-hard key
derivation (scrypt) and the bundle is sealed with AES-256-GCM, so only someone with the passphrase can open
it. On import, the destination install re-encrypts every secret under **its own** key.

<Callout title="The passphrase is optional — but read this first" type="warn">
If you **skip** the passphrase, the export carries **no secrets at all** — you'll re-enter environment
variables and credentials by hand after importing. If you **set** one, guard it: the passphrase is the only
thing that opens the sealed secrets, and a wrong (or lost) passphrase can't be recovered. It's checked
*before* any data is written, so a bad passphrase aborts the import cleanly.
</Callout>

### Import modes

When you import, you choose how it merges with whatever is already there:

- **Replace (wipe)** — clears the instance and loads the file wholesale. This replaces the current user and
  session, so the dashboard reloads afterward to sign you in against the imported data. This is the default.
- **Merge** — inserts only rows that don't already exist, and re-hydrates secrets **only** for the rows it
  actually added, so a row already on the destination is never clobbered.

### Move an instance to a new machine

<Steps>

<Step>

### Export from the old install

Open **Settings → Instance**, and in the **Export data** card set a passphrase (typed twice to confirm),
then press **Export & download**. Your browser saves an `synq-export-…json` file. From the terminal it's:

```bash
synq system data-transfer export --passphrase 'your-secret' --out synq-export.json
```

</Step>

<Step>

### Import on the new install

Install Synq on the new machine, sign in as the owner, then go to **Settings → Instance** and press
**Import from file…**. Pick the file, enter the same passphrase, choose **Replace everything** for a clean
move, and confirm. Or from the CLI:

```bash
synq system data-transfer import --file synq-export.json --passphrase 'your-secret' --mode wipe
```

</Step>

<Step>

### You're done

The new instance now holds your projects, settings, and (if you used a passphrase) your secrets — re-encrypted
under the new install's key. Everything that pointed at the old machine still points at it, so redeploy when
you're ready.

</Step>

</Steps>

<Callout title="Import size limit" type="info">
The import endpoint accepts files up to **500 MB**. A larger export means a very large instance — split it
by moving individual projects instead (below), or import onto a full PostgreSQL-backed instance.
</Callout>

## Moving a single project

You don't have to move the whole instance to relocate one project:

- **Export one project**: **Project → Advanced → Export project** writes a file with just that project (and
  the environments you pick), which **Settings → Instance → Import data** reads on another instance.
- **Move it between servers**: [Server-to-server migration](/docs/guides/server-to-server-migration) moves a
  running app, and its data, from one of your servers to another.

## How Synq guards an embedded data directory

When Synq runs on the embedded database (PGlite), a few things keep that on-disk data safe:

- **A single-instance lock.** PGlite is single-process: if two processes opened the same data directory at
  once, the database would be corrupted. Before opening, Synq atomically claims a `<data-dir>.lock` file
  next to (never inside) the directory. A second copy waits briefly, then fails with a clear message rather
  than racing in. The lock self-heals after a crash (a dead process's lock is reclaimed on next launch) and
  refuses to open a directory locked by a *different machine*, so a synced folder can't quietly corrupt it.
- **Its own data folder.** The database lives in `~/.synq/data` (or wherever `PGLITE_DATA_DIR` points),
  separate from any project checkout.

<Callout title="Your data isn't trapped in the embedded database" type="info">
The embedded database is the same format as every other install, so the **Export** flow above moves it to a
server or a Docker stack with no conversion step. The local Synq that desktop app versions up to 1.2.5 could
run worked the same way; see [the desktop app](/docs/getting-started/desktop-app#updating-from-version-125-or-earlier)
to export its data.
</Callout>

## Related guides

<Cards>
  <Card title="Runtime model" href="/docs/architecture/runtime-model" description="What actually runs where when you deploy: this machine or your own server." />
  <Card title="Installation" href="/docs/getting-started/installation" description="Install Synq on your server with one command and your license key." />
</Cards>
