# Isolation
URL: https://synquic.tech/docs/security/isolation.md

How Synq keeps deployments and projects apart — separate containers, per-project networks and volumes, and enforced resource limits.

Think of your Synq server like an apartment building. Every app you deploy gets its own unit: its own front door, its own electricity meter, its own mailbox. Neighbors can't wander into your unit, and one noisy tenant can't drain the whole building's power. This page explains the walls Synq puts up between your apps — what each one is, and how strong it is on each runtime.

## Scoping comes first

Before anything runs, access is decided by **organization** (a workspace that owns projects) and **project**. Every API request passes through one permission check, and an organization id from the client is only trusted if you're actually a member. So one team can never list, deploy, or tear down another team's projects. That control-plane boundary is covered in the [permission model](/docs/security/permissions); the rest of this page is about the walls between apps *at runtime*, once a deploy is live.

Everything Synq creates on a host is stamped with the project and deployment it belongs to, using Docker **labels** (key/value tags attached to a container):

- `synq.project` — which project owns it.
- `synq.deployment` — which specific deploy created it.
- `synq.service` — which service, for multi-service (compose) projects.
- `synq.build` — which build session produced a build image, so half-finished build artifacts can be reclaimed too.

These labels are how Synq reliably finds and cleans up only its own resources — for example, reclaiming a leftover container from a deploy that failed halfway, without touching anything else on the machine.

## One container per deployment

On the **Docker** runtime, each deployment runs inside its own **container** — a sandboxed box with its own filesystem, processes, and network view, isolated from the host and from other containers by the Linux kernel. Containers are named predictably so two never collide:

| What | Name pattern |
|---|---|
| Single-app deployment | `synq-<project>-<deployment>` |
| A service in a compose project | `synq-<slug>-<service>` |
| Project network | `synq-<slug>` |
| Project-scoped named volume | `synq-<slug>-<name>` |

A single-app container publishes on a **random host port**, and Synq's built-in router (**OpenResty**, an nginx-based reverse proxy) forwards traffic to it — so nothing is exposed on a predictable port that another app could clash with.

## Per-project networks

For a project with multiple services (say a web app plus a database), Synq creates one private **bridge network** — a virtual LAN that only the containers attached to it can use — named `synq-<slug>`. Every service in that project joins it and can reach the others by service name, like `http://database:5432`.

Because each project gets its **own** network, a service in one project cannot address a service in another by name. And a service only opens a port to the outside world if it explicitly asks to — otherwise it's reachable only by its siblings inside the project network.

<Callout title="Names are project-private" type="info">
Two different projects can both have a service called `api` or `db` without any conflict — each name resolves only inside its own project's network.
</Callout>

## Per-project volumes

A **named volume** is a managed disk that Docker keeps around between restarts (databases use them so data survives a redeploy). The catch: if two projects both ask for a volume literally named `postgres_data`, plain Docker hands them the *same* disk — silent cross-project data mixing.

Synq prevents this by prefixing every named volume with the project: `synq-<slug>-<name>`. Two projects that both declare `postgres_data` get two separate disks that can never touch. Bind mounts (a specific host path you chose) and anonymous volumes are left exactly as written.

<Callout title="Older services and name clashes" type="warn">
Services created before volume-scoping keep their original bare volume names. To protect them, Synq refuses to deploy a **new** project whose volume name is already in use by another project's container, and tells you to rename it — so no deploy can quietly adopt another project's data.
</Callout>

## Resource limits

Each deployment is given a budget so one app can't starve the others. A budget has three parts:

- **CPU** (`cpuCores`) — fractional cores, e.g. `0.5`, `1`, `2`.
- **Memory** (`memoryMb`) — a hard ceiling in megabytes.
- **Disk** (`diskMb`) — writable disk in megabytes.

Defaults for a running app are **1 core, 512 MB memory, and 5 GB disk**. Builds get a bigger budget (up to **4 cores and 8 GB**) because bundlers are memory-hungry, then the running app drops back to its production size. You can change a project's budget from its settings, within these ranges: CPU `0.25`–`4`, memory `128`–`8192` MB, disk `64`–`204800` MB.

How strictly the budget is enforced depends on the runtime. On Docker, **memory is a hard cap** — cross it and the container is killed and restarted — while **CPU is a relative weight**: under contention a `2`-core app gets roughly twice the CPU time of a `1`-core one, but an idle machine lets either use what's free.

## How the runtimes compare

Synq can run your app two ways, and the isolation strength differs. Pick the tab that matches where you deploy.

<Tabs items={['Docker', 'Server (no Docker)']}>

<Tab value="Docker">

The strongest isolation. Every deployment is its own container with:

- kernel-level separation from the host and other apps,
- a hard **memory** cap and a **CPU** weight, and
- a random published port fronted by the router — never a fixed port another app could grab.

Multi-service (compose) projects add a **private per-project network** and **project-scoped named volumes** on top of that. This is the recommended runtime whenever Docker is available.

</Tab>

<Tab value="Server (no Docker)">

For hosts without Docker, Synq runs your app **directly as a process** on the machine, supervised by systemd (Linux) or a lightweight `nohup` fallback (macOS). Each deployment gets its own release directory and its own supervised process (on Linux, a dedicated systemd unit named `synq-<deployment>.service`).

This is lighter, but the walls are thinner: processes **share the host's filesystem, network, and users**, and this mode does **not** enforce CPU or memory caps. systemd additionally gives you automatic restart-on-crash and reboot survival; the `nohup` fallback does not.

<Callout title="Weaker isolation without Docker" type="warn">
Because bare processes share the host and aren't resource-capped, run only apps you trust on a shared bare host, and prefer the Docker runtime for anything multi-tenant.
</Callout>

</Tab>

</Tabs>

## Related guides

<Cards>
  <Card title="Permission model" href="/docs/security/permissions" description="Roles, resource grants, and the single check every request passes through." />
  <Card title="Authentication" href="/docs/security/auth" description="Sessions, tokens, and how credentials are custodied." />
</Cards>
