Skip to content
edgesync Artifact [5ff56ae9e9]
Login

Artifact [5ff56ae9e9]

Artifact 5ff56ae9e9199c285632b1509c861157e679bcfe5254859e40ae2a67c8cf9911:


---
title: Deployment
weight: 30
---

A working production recipe for EdgeSync, derived from a reference deployment at `sync.example.com`. The shape: one VPS running NATS + a leaf agent, exposed publicly via Cloudflare Tunnel and privately via Tailscale.

## Topology

```
public internet
      │
      ▼ HTTPS
Cloudflare Tunnel ──► VPS:9000 (leaf HTTP /xfer)
                         │
                         ▼
                  leaf agent ◄────► NATS:4222 (Tailscale-only)
                         │
                         ▼
                   .fossil repo (volume mount)

tailnet ──────────────► VPS:9000 (HTTP)
                  └───► VPS:4222 (NATS)
```

## Files

The reference deployment lives in `deploy/`:

```
deploy/
├── Dockerfile              # multi-stage Go build, GOWORK=off
├── docker-compose.yml      # NATS on Tailscale IP, leaf on :9000
└── data/                   # host volume mount with .fossil files
```

### Dockerfile

The leaf binary is built standalone (libfossil pulled from the public Go module proxy) so no private-module auth is needed. The build uses `GOWORK=off` because Docker copies only `leaf/`, not the whole workspace.

### docker-compose.yml

Two containers:

- **`nats`** — official `nats:latest` image, bound to the host's Tailscale interface only (`100.64.0.1:4222`)
- **`leaf`** — built from `Dockerfile`, exposed on `:9000`

Ports `8080` and `8090` are intentionally avoided because they are occupied by Coolify and Caddy on the reference VPS. Adjust as needed.

### Cloudflare Tunnel

```yaml
# ~/.cloudflared/config.yml on the VPS
tunnel: <tunnel-id>
credentials-file: /home/user/.cloudflared/<tunnel-id>.json
ingress:
  - hostname: sync.example.com
    service: http://localhost:9000
  - service: http_status:404
```

Run as a systemd service: `cloudflared-fossil`.

## Update flow

```sh
# On the VPS
cd ~/EdgeSync && git pull
cd deploy && sudo docker compose up -d --build
```

This rebuilds the leaf container with the latest code and restarts both services. NATS does not need to restart for a leaf upgrade.

## Endpoints

| Endpoint | URL | Access |
| --- | --- | --- |
| Public HTTPS | `https://sync.example.com` | Cloudflare Tunnel (anyone with the URL) |
| Tailscale HTTP | `http://100.64.0.1:9000` | Tailnet only |
| Tailscale NATS | `nats://100.64.0.1:4222` | Tailnet only |

## Mobile clients

For Expo / React Native clients (e.g. `edgesync-notify-app`), you'll typically:

1. Run a NATS leafnode on the VPS bound to port `7422` (separate from the regular `4222` so leafnode auth can be scoped)
2. Open the leafnode port to your tailnet (`ufw route allow ...`)
3. In the app, configure the NATS leafnode URL and pair via QR (see [Notify](./notify))

## Observability

Set `OTEL_EXPORTER_OTLP_ENDPOINT` and (if exporting to Honeycomb) the API key. EdgeSync uses [Doppler](https://doppler.com) to manage OTel secrets without committing `.env` files:

```sh
doppler run -- docker compose up -d --build
```

When the env var is unset the agent runs with a no-op observer — telemetry is zero-cost when not configured.

The reference Honeycomb dashboard ("EdgeSync Sim — Operational Overview") tracks sync latency, convergence, file throughput, and errors across the dataset `edgesync-sim` in the `test` environment.