> ## Documentation Index
> Fetch the complete documentation index at: https://vibex.peatboy.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloud development and remote development

> Run Vibex on a server, or reach your own desktop from your phone.

There are two ways to keep working when you are away from the machine that runs your Agents. Pick the one you need:

| What you want                                                                         | What this path is called                                        | Where the Agent runs | Who connects                          |
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------- | ------------------------------------- |
| One environment on a server that your work laptop, home computer, and phone all share | Cloud development: run the **headless runtime**, `vibex-server` | The server           | Desktop and mobile connect as clients |
| Your phone or tablet reaching the computer on your desk                               | Remote development: desktop + mobile                            | Your own computer    | Only the mobile devices you paired    |

<Info>
  **What is a headless runtime?** Think of it as Vibex without a window. It owns Agent sessions, workspaces, Git, terminals, and provider profiles, and behaves exactly like the desktop app — it just runs on a server, and you operate it through a desktop or mobile client. Its program name is `vibex-server`.
</Info>

Both paths establish trust with a **one-time pairing**: the first time, you need the pairing code or QR code from the machine itself. After that, long-lived credentials live on both sides and clients reconnect on their own.

The steps below assume the desktop app is already installed. The Android client can be sideloaded from GitHub Releases; iOS currently ships only a simulator build. See [Install and platforms](/docs/en/install) for both.

## 1. Cloud development: run Vibex on a server

Agents read and write code on the server and run commands there, so prepare the server first and connect clients afterwards.

### Before you start

* A Linux server with Docker. For a single user, 1 vCPU and 2 GB of RAM is enough to start; running several Agents benefits from 2 vCPU and 4 GB or more.
* Your code must be reachable on the server: either clone it there, or mount a host directory into the container (the example below mounts one).
* Agent accounts and API keys are configured later, from the desktop client: add them under **Config Center** (see [Config Center](/docs/en/config-center)). **Never** put them in the compose file or commit them.

### Step 1: start the runtime

The project repository already contains a complete `docker-compose.yml` deployment file — open it directly: [deploy/server/docker-compose.yml](https://github.com/vibex-ai/vibex/blob/main/deploy/server/docker-compose.yml). The version below is a minimal example trimmed for this walkthrough; save it as `docker-compose.yml` in any directory on the server:

```yaml theme={null}
services:
  vibex-server:
    # Use a moving channel tag: rc follows the newest release candidate,
    # latest follows the newest stable release; pin a version only if you need to
    image: ghcr.io/vibex-ai/vibex-server:rc
    container_name: vibex-server
    # Restart the runtime after a reboot; the healthcheck below decides when it is up
    restart: unless-stopped
    # Share the host network namespace: the container's loopback is the host's loopback,
    # which avoids publishing ports on a bridge network with a non-loopback bind
    # (loopback mode refuses that bind by design)
    network_mode: host
    environment:
      # Runtime data directory: database, server identity, and Agent installs live here
      VIBEX_HOME: /data
      VIBEX_DB_PATH: /data/vibex.db
      # With 127.0.0.1 here, phones and other computers cannot connect
      VIBEX_BIND_ADDR: 0.0.0.0:8765
      # lan allows local-network access; a public deployment needs public plus HTTPS
      VIBEX_DEPLOYMENT_MODE: lan
      # The runtime signs its own certificate; clients receive the fingerprint
      # inside the pairing connection string, so no certificate authority is needed
      VIBEX_TLS_MODE: pinned_certificate
      # The address clients dial; the port must match VIBEX_BIND_ADDR
      VIBEX_PUBLIC_HOST: 192.168.1.10:8765
      # Directories a client may browse when picking a project (used in step 4)
      VIBEX_WORKSPACE_ROOTS: /data:/data/repos
    volumes:
      # The only volume you have to back up: database plus server identity
      - vibex-data:/data
      # Your code directory; after mounting it, list its path in VIBEX_WORKSPACE_ROOTS
      - /srv/repos:/data/repos        # change to the code directory Agents should open
volumes:
  vibex-data:
```

This example only uses the variables you cannot avoid; the full list, including rate limits, event capacity, and secret storage, is in the [environment reference](/docs/en/reference/environment-variables).

<Note>
  Use `rc` to follow the newest release candidate or `latest` for the newest stable release; pin a specific version only if you need to. If you already cloned the repository, you can also build from source with its deployment files: `docker compose -f deploy/server/docker-compose.yml up --build -d vibex-server`.
</Note>

Start it and read the pairing material:

```bash theme={null}
docker compose up -d
docker logs vibex-server | grep -E 'pairing_(code|link)'
```

The log prints two things:

* `pairing_code=123-456-789` — a one-time code that expires after five minutes by default.
* `pairing_link=vibex://pair#/code/...` — a connection string that already carries the server address, the one-time code, and the server certificate (DER). Copy the whole string; the client pins that certificate before its first request, so a server with a self-signed certificate needs no system CA.

<Warning>
  A pairing code or connection string is as sensitive as a password and works once. Do not post it in chat, an issue, or a screenshot. When it expires, mint a new one on the server.
</Warning>

```bash theme={null}
docker exec vibex-server vibex-server pairing-code
```

### If the server is on the public internet

A public deployment adds exactly one requirement: **HTTPS**. Let Caddy obtain the certificate while the runtime only listens on loopback:

```yaml theme={null}
services:
  vibex-server:
    image: ghcr.io/vibex-ai/vibex-server:latest
    container_name: vibex-server
    restart: unless-stopped
    network_mode: host
    environment:
      # Loopback only; public traffic goes through Caddy and 8765 stays closed
      VIBEX_BIND_ADDR: 127.0.0.1:8765
      # A public deployment refuses to start without trusted HTTPS
      VIBEX_DEPLOYMENT_MODE: public
      VIBEX_TLS_MODE: trusted_https_proxy
      # Let rate limits use the real client address; only true behind a TLS proxy
      VIBEX_TRUST_FORWARDED_HEADERS: "true"
      # The domain clients use, and the address printed in the pairing string
      VIBEX_PUBLIC_HOST: vibex.example.com
      # Accept only these hosts and origins; they must match the domain above
      VIBEX_ALLOWED_HOSTS: vibex.example.com
      VIBEX_ALLOWED_ORIGINS: https://vibex.example.com
      # Directories a client may browse when picking a project
      VIBEX_WORKSPACE_ROOTS: /data:/data/repos
    volumes:
      # The only volume you have to back up: database plus server identity
      - vibex-data:/data
      - /srv/repos:/data/repos

  # Reverse proxy: obtains and renews the certificate, forwards 443 to the runtime's loopback port
  caddy:
    image: caddy:2-alpine
    container_name: vibex-caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config

volumes:
  vibex-data:
  caddy-data:
  caddy-config:
```

Add a `Caddyfile` next to it:

```text theme={null}
vibex.example.com {
    reverse_proxy 127.0.0.1:8765
}
```

```bash theme={null}
docker compose up -d
docker logs vibex-server | grep -E 'pairing_(code|link)'
```

Your firewall only needs `80`, `443`, and SSH. Keep `8765` on loopback and out of the public internet. After you replace the domain and address with your own, `VIBEX_ALLOWED_HOSTS` and `VIBEX_ALLOWED_ORIGINS` must match the domain clients actually use, or requests are rejected.

<Tip>
  If you would rather not expose anything to the public internet, install Tailscale on the server and publish the runtime with `tailscale serve --bg http://127.0.0.1:8765`. Set `VIBEX_DEPLOYMENT_MODE` to `public`, `VIBEX_TLS_MODE` to `trusted_https_proxy`, and use the Tailscale hostname for both `VIBEX_PUBLIC_HOST` and `VIBEX_ALLOWED_HOSTS`.
</Tip>

### Step 2: connect the desktop

1. Select the **Runtime** button in the title bar to open the runtime manager.
2. Select **Add runtime…**, choose the **Connection string** tab, paste the whole `vibex://pair#/code/...` value, and select **Pair**. This also works when the server uses a self-signed certificate, because the fingerprint travels inside the string.
3. Alternatively, choose the **Pairing code** tab and enter the **Server address** and **One-time pairing code**.
4. After pairing, the runtime's **Certificate** row shows a `sha256:` fingerprint. Compare it with `tls_fingerprint` in the server log.
5. Select **Switch to this runtime** to make it current. From now on, sessions, files, Git, and terminals in the desktop come from the server. To go back to your own machine, select **This device** in the list and switch to it. To remove the runtime, select **Remove** in its details — that deletes only the local device credential; the server-side authorization stays until it is revoked on the server.

### Step 3: connect the mobile client

1. Open the mobile app and choose **Pair with a Cloud Server**.
2. Enter the **server address** (for example `192.168.1.10:8765`) and the **pairing code**, then select **Pair with Code**.
3. When the server uses a self-signed certificate, paste the `vibex://pair#/code/...` string into the **connection string** field instead; it carries the certificate too.
4. Confirm the pairing; what this device may do comes from the server when it mints the pairing code, and defaults to **Full control**. To grant less, mint a new code on the server with `docker exec vibex-server vibex-server pairing-code --permission read-only` (choices: `read-only`, `approve-only`, `full-control`).

### Step 4: let Agents see your code

Agents run on the server, so **they only see directories inside the server (container)**. Your code therefore has to live there:

* Clone it on the server into `/data/repos/your-project`, or
* Mount a host directory into the container (the `- /srv/repos:/data/repos` line above).

Then make sure the container path is listed in `VIBEX_WORKSPACE_ROOTS` (the `/data:/data/repos` value above). In any client, open a new session, select the **project directory** picker, choose **Choose another directory**, and pick your project under `/data` or `/data/repos`.

<Note>
  Mounting a directory without listing it in `VIBEX_WORKSPACE_ROOTS` leaves it unbrowsable for clients; listing a path without mounting it leaves the directory nonexistent inside the runtime.
</Note>

### Step 5: day-two operations

| Task                     | How                                                                                                                                                                                                                                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Upgrade to a new version | Change the version in `image`, then `docker compose pull && docker compose up -d --no-build`. With moving tags such as `rc` or `latest`, the `pull` is required or the container stays on the old image                                                                                                                                     |
| Back up                  | Back up the `/data` volume. It holds the database and the server identity; losing the identity forces every device to pair again                                                                                                                                                                                                            |
| Revoke a device          | On a headless authority, run `docker exec vibex-server vibex-server revoke DEVICE_ID` on the server. When the desktop is the authority, open **Pair mobile → Paired devices (N)** and select **Revoke** there. Selecting **Remove** on a client runtime deletes only the local credential; it does not revoke the server-side authorization |
| Check the runtime        | `docker exec vibex-server vibex-server status`; after changing environment variables, run `docker exec vibex-server vibex-server config-check` first                                                                                                                                                                                        |
| Remove the runtime       | `docker compose down`. **Never add `-v`**, which deletes `/data` as well                                                                                                                                                                                                                                                                    |

## 2. Remote development: your phone reaching your desktop

Your computer stays the authority: Agents, files, Git, and terminals all run on the desktop, and the phone only gets a window to view and operate them. Use this when you are away and want to check progress, answer a question, or approve a request.

### Before you start

* The desktop app is running and the computer stays awake; sleep drops the connection.
* The phone and the computer can reach each other: the same Wi-Fi is easiest, and Tailnet or a self-hosted Relay cover the rest.

### Step 1: publish remote access from the desktop

1. Select the **pair mobile device** icon in the top toolbar. The dialog has two tabs: **Pair** publishes a new pairing, and **Paired devices (N)** manages the devices that already hold a grant.
2. Choose how the phone will connect:
   * **Tailnet** (recommended) — private access through Tailscale or another private network; good when you are not on the same Wi-Fi.
   * **Direct HTTPS** — you have your own HTTPS address that points at this computer.
   * **Self-hosted Relay** — the phone and computer cannot reach each other directly, so your own Relay forwards encrypted traffic.
3. Fill in the address the dialog asks for (Direct expects `https://your-address`, Relay expects `https://your-relay`), choose what this phone may do (the default is **Read only**; see [Permission levels](#permission-levels)), and select **Publish**.
4. A QR code and a pairing link appear: **scan to pair**, or select **Copy link** and send it to the phone. The link expires quickly; select **Regenerate** when it does.

<Warning>
  A pairing link or QR code is a one-time secret. Do not publish it. When the phone pairs, the desktop asks for confirmation, and pairing only succeeds if you approve it.
</Warning>

### Step 2: pair the phone

* On the same Wi-Fi: the mobile app discovers **nearby desktops**; select yours and confirm.
* On another network: scan the QR code or open the copied `vibex://` link.
* Manually: choose **Pair with Code** in the mobile app and enter the address and pairing code shown on the desktop.

The permission level is chosen on the desktop when it publishes the pairing offer, and the desktop asks you to confirm the device when the phone claims it. The phone then shows the desktop's session list.

### Step 3: what the phone can do

| Capability                | Notes                                                                                                       |
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Sessions                  | Read the timeline, send follow-up messages, rename, delete, fork, and multi-select.                         |
| Approvals and elicitation | Approve or reject permission requests and fill in elicitation forms.                                        |
| Files                     | Browse the file tree, search by name or content, and **edit and save files**.                               |
| Git                       | View status and diffs, stage / unstage / revert / commit, view history, fetch / push, and create worktrees. |
| Terminal                  | Create, attach, resize, and send input.                                                                     |
| Usage                     | Shows only the session count, with the note "Usage details are available on the desktop host."              |

**Mobile has no Config Center**, so it cannot manage Agents, providers, MCP, or Skills. Its Settings contain only Connection, Session timeline, Appearance, Notifications, and About.

Every write is ultimately produced by the authoritative runtime; mobile shows its projection.

### When direct access fails: self-hosted Relay

Phones and computers are often behind corporate networks or carrier NAT, so they cannot reach each other. Run the Relay on a server both sides can reach; it only forwards encrypted frames and cannot read them:

```yaml theme={null}
services:
  relay-server:
    image: ghcr.io/vibex-ai/vibex-relay-server:rc
    container_name: vibex-relay-server
    restart: unless-stopped
    # Publish the port on the host's loopback only; the public entry point is an HTTPS proxy
    ports:
      - "127.0.0.1:9700:9700"
    environment:
      # Listen on every interface inside the container, or the published port cannot reach it
      VIBEX_RELAY_BIND_ADDR: 0.0.0.0:9700
      # Room, connection, and rate limits all have defaults; add the variables from the
      # Relay section of the environment reference only when you need to change them
```

```bash theme={null}
docker compose up -d
curl -fsS http://127.0.0.1:9700/health
```

To reach it over the public internet, give it an HTTPS/WSS entry point (for example Caddy reverse-proxying to `127.0.0.1:9700`), then publish remote access on the desktop with **Self-hosted Relay** and enter `https://your-relay-domain`.

The Relay is pure forwarding: it does not store sessions, files, or provider profiles, and it does not decrypt content. Rooms and connections live in memory, so a restart clears them and devices rebuild them on reconnect.

### Disconnects and reconnects

After a network change, screen lock, or app resume, clients re-authenticate and catch up on missing history: they show authoritative session and timeline state first, then resume live updates.

| State             | What to do                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| `stale`           | Wait for the authority to refresh; do not repeat commands with side effects from an old snapshot.      |
| `revoked`         | The device was revoked and must pair again.                                                            |
| `resync_required` | The client detected a sequence or cursor gap and is resyncing; treat the authority's state as current. |

## Permission levels

Choose a permission when you pair a mobile device, and grant the smallest one that works. The level is decided by the **initiating side** when it creates the pairing, and a client cannot raise it on its own:

| Level                             | What it can do                                                                                                                   | Use it when                             |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| **Read only** (`read_only`)       | Read sessions, file summaries, Git status and diffs, provider summaries; every write is rejected and recorded in the audit trail | You only want to follow progress        |
| **Approve only** (`approve_only`) | Everything above, plus resolving Agent permission requests and elicitation forms                                                 | You want to approve requests while away |
| **Full control** (`full_control`) | Send messages, write files, type into terminals, and perform Git writes, still capped by desktop capabilities                    | You really want to work from the phone  |

Revoke devices on the authoritative side:

* When the **desktop is the authority**, open **Pair mobile**, switch to the **Paired devices (N)** tab, and select **Revoke** on the device, then confirm. Each row shows the device's status, permission, and last activity, and **Refresh devices** reloads the list; more than six devices are paged.
* When a **headless runtime is the authority**, run `docker exec vibex-server vibex-server revoke DEVICE_ID` (or `vibex-server revoke DEVICE_ID` on the server).

Revocation closes the device's active connections immediately, and it has to pair again afterwards. Selecting **Remove** on a client runtime deletes only the local credential; the server-side device authorization stays.

## Security checklist

* Keep the runtime's `8765` port on loopback or your private network, and always use HTTPS on the public internet. The authority listens on loopback by default; never run an unauthenticated public listener.
* Grant the smallest permission level; use **Full control** only when you need it.
* Never post pairing codes, QR codes, or connection strings publicly; each one works once, and none is a long-lived credential.
* Revoke devices you no longer use.
* A Relay only forwards encrypted traffic. It is not a permission boundary, and business data does not belong in its logs.

## Troubleshooting first steps

| Symptom                                                           | Likely cause                                                                         | What to do                                                                                                       |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| The phone reports an expired pairing                              | Pairing codes and QR codes are short-lived                                           | Select **Regenerate** on the desktop and scan again                                                              |
| The phone cannot reach the desktop                                | Different networks, or the router/NAT blocks the route                               | Use Tailnet, or deploy a self-hosted Relay                                                                       |
| The desktop cannot reach the server                               | Wrong address or port, or a closed firewall                                          | Reach the address from the pairing string on the server itself, and check that `VIBEX_BIND_ADDR` is not loopback |
| A client reports a rejected certificate or host                   | `VIBEX_PUBLIC_HOST`, `VIBEX_ALLOWED_HOSTS`, and the address clients use do not agree | Align them with the real domain or address and restart the container                                             |
| A project directory is missing from the picker                    | The code is not on the server, or the path is not in `VIBEX_WORKSPACE_ROOTS`         | Mount it and add it as described in step 4                                                                       |
| The session list stops updating                                   | The client is still catching up after a network change                               | Let it finish or refresh manually; if it stays stuck, check that the desktop or server still runs                |
| The phone reports a revoked device                                | The authority revoked that device                                                    | Pair it again                                                                                                    |
| The desktop keeps using the server and you want the local runtime | The stored pairing credential restores automatically                                 | Open the **Runtime** panel, select **This device**, and switch to it                                             |

## Environment variables

The examples above use only the variables you cannot avoid. The full list of `vibex-server`, Relay, and desktop variables, with defaults and purposes, is in the [environment reference](/docs/en/reference/environment-variables). Deployment forms, commands, and health checks are in [Self-hosted headless runtime](/docs/en/self-hosted-server), and the Relay's endpoints, limits, and push configuration are in [Self-hosted Relay](/docs/en/relay).

For deeper details on the pairing handshake, encryption, and sync contracts, read [Remote v2 and Relay](/docs/en/developer/remote-protocol) in the developer guide.
