# Runtime model
URL: https://synquic.tech/docs/architecture/runtime-model.md

The two places an app can run, this machine or your own server over SSH, and how a build and deploy flow in each.

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

Synq can run your app in two different places. Think of your finished app as a meal: you can cook and
serve it in **your own kitchen** (the machine Synq runs on), or send the recipe to a **friend's kitchen**
(another server of yours). The food is the same; only the kitchen changes.

You pick the kitchen when you deploy. Synq calls it the **deploy target**, and there are two:

| Target | Where it runs | How Synq reaches it |
|---|---|---|
| **Local** | The same machine Synq itself is running on | Directly, no network hop |
| **Server** | Another machine of yours (a VPS, a box in a closet) | Over **SSH** (a secure remote connection) |

The code also knows a third target, **Cloud**, which hands the app to a hosted cloud. It's switched off in
Synq, so the deploy screen never offers it.

<Callout title="You can change your mind">
The target is a per-deploy choice. Deploy on the Synq server today to try something, then redeploy the same
project to a dedicated server later; nothing about the app has to change.
</Callout>

## Two ways to run it: sandboxed or direct

For both targets, Synq also picks *how* the app runs on that machine.
This is the **isolation mode**, and there are two:

- **Sandboxed (Docker)** — your app runs inside a **container**: a sealed box with its own files and
  dependencies, kept apart from everything else on the machine. This is the default.
- **Direct (bare)** — your app runs as a plain process on the machine, with no box around it. Lighter, but
  it shares the machine's environment.

Sandboxed is the default everywhere because the isolation is worth it and the overhead is tiny. The one time
Direct earns its keep is on a very small machine — under about **2 GB of RAM** — where the container engine
would compete with your app for memory. Synq surfaces that as a note when you pick Direct, rather than
silently switching for you.

<Callout title="How a Direct app stays running" type="info">
A Direct (bare) app is kept alive by the machine's own process supervisor — **systemd** on Linux,
**nohup** on macOS — so it restarts if it crashes or the box reboots. A Sandboxed app is kept alive by Docker.
</Callout>

## Build first, then hand off

Whatever the target, every deploy runs the **same two stages** in order.

<Steps>

<Step>

### Build — turn source into something runnable

The build pipeline always runs the same steps: **prepare → clone → install → build**.

- **Prepare** gets the environment ready (installs the toolchain, transfers local files) — this is counted
  separately so first-time setup doesn't inflate your build time.
- **Clone** fetches your code (skipped when you're deploying a local folder — the files are already there).
- **Install** runs your install command (e.g. `npm install`).
- **Build** runs your build command (e.g. `npm run build`) — skipped if the app doesn't need one.

</Step>

<Step>

### Deploy — start the new version and send traffic to it

Once the build succeeds, Synq starts the new version, waits for it, points your web address at it, and
then stops the old one. For Sandboxed apps this happens **without downtime**: the old version keeps
serving until the exact moment traffic is repointed, so a failure before that moment leaves the old one
untouched. See [Zero-downtime and auto-revert](#zero-downtime-and-auto-revert) below.

</Step>

</Steps>

<Callout title="Where the build runs is a separate choice">
*Where the build happens* is separate from *where the app runs*. For a server deploy you can build
on **This Machine** — the machine running Synq — and transfer only the finished output to the target, or
build **on the deploy target** itself. Synq suggests one based on your framework — for most stacks that's
**This Machine** — and you can change it per deploy or save a default under **Settings**. Building on This
Machine is faster when it's the more powerful box and spares a small target VPS a heavy build; building on the
target is best when the Synq machine has limited resources.
</Callout>

## The two targets, up close

<Tabs items={['Local', 'Your server']}>

<Tab value="Local">

**Local** runs your app on the very machine Synq is installed on. That's the common single-server setup:
Synq and your apps share one box.

- **Sandboxed** talks to the machine's Docker socket directly.
- **Direct** runs the process on the machine and supervises it with systemd or nohup.

Web addresses and HTTPS are handled by Synq's built-in router on that same machine (see
[Routing and certificates](#routing-and-certificates)).

</Tab>

<Tab value="Your server">

**Server** runs your app on a machine you own, reached over **SSH** — the same secure connection you'd use
from a terminal. You add the server once — on the **Servers** page, or right in the deploy flow with **Add
your own server** — and Synq keeps a pooled connection to it, so repeat deploys reuse the same link
instead of dialing in fresh each time.

- **Sandboxed** drives the remote machine's Docker engine over an SSH tunnel.
- **Direct** runs shell commands on the server over that same SSH connection.

The router and certificate tooling are set up on *your* server, so traffic never detours through Synq.

<Callout title="No server yet?" type="warn">
If you choose the Server target but haven't added a machine, the deploy screen offers **Add your own server**
so you can connect one over SSH right there — and you can manage servers any time on the **Servers** page.
See [Deploy to your own server](/docs/guides/custom-servers).
</Callout>

</Tab>

</Tabs>

## Routing and certificates

Once your app is running, something has to send visitors to it and serve HTTPS. On both targets Synq uses
**OpenResty** (a build of the nginx web server) to route requests, and **certbot** to get free HTTPS
certificates from **Let's Encrypt** and renew them automatically.

For attaching your own domain, verifying DNS, and managing certificates, see the
[Domains API](/docs/api/domains) and [Add a custom domain](/docs/guides/custom-domains).

## Zero-downtime and auto-revert

The deploy stage adapts to whether the new version can run **alongside** the old one:

- **Sandboxed apps can overlap.** Each version gets its own container, so Synq
  starts the new one, checks it, repoints traffic, and only *then* stops the old one. If anything fails before
  the switch, the old version was never touched — it keeps serving, and the failed deploy simply doesn't take.
- **Direct apps can't overlap**, because the new process needs the same fixed port the old one is holding. So
  Synq stops the old process, starts the new one, and — if the new one fails — restarts the old one to
  bring you back. This path has a brief unavoidable gap while the port changes hands.

<Callout title="One deploy at a time per machine" type="info">
Deploys to the same machine are serialized with a lock, so two projects deploying at once can't clobber each
other's router config or shared state.
</Callout>

## Related guides

<Cards>
  <Card title="Deploy from GitHub" href="/docs/guides/deploy-from-github" description="The most common path: repo in, live URL out." />
  <Card title="Deploy to your own server" href="/docs/guides/custom-servers" description="Add a machine over SSH and ship to it." />
  <Card title="Custom domains" href="/docs/guides/custom-domains" description="Put your app on your own address with automatic HTTPS." />
</Cards>
