DocsDeploySelf-host

Self-host the server

Run the TopGun server on your own infrastructure using Docker, Docker Compose, or Kubernetes. The server keeps its data in an embedded store (redb) inside the process — there is no database to run and no external dependency. Give it a persistent volume and your data survives restarts.

Building the image

terminal
# From the root of the repository
docker build -t topgun-server -f deploy/Dockerfile.server .

The Dockerfile uses a multi-stage Rust build (rust:1.85-bookworm) and produces a minimal debian:bookworm-slim runtime image. The binary is named topgun-server and is the same binary used in production-style demos.

Docker

Run with the embedded store (default)

terminal
# Run with the embedded store (default — no database to run)
docker run -d \
  --name topgun-server \
  -p 8080:8080 \
  -e RUST_LOG=topgun_server=info \
  -v topgun-data:/data \
  -e TOPGUN_DATA_DIR=/data/topgun-data \
  -e TOPGUN_REDB_PATH=/data/topgun.redb \
  -e TOPGUN_WAL_DIR=/data/topgun-wal \
  topgun-server

Docker Compose

Single node

docker-compose.yml
version: '3.8'

services:
  server:
    build:
      context: .
      dockerfile: deploy/Dockerfile.server
    container_name: topgun-server
    ports:
      - "8080:8080"
    environment:
      RUST_LOG: topgun_server=info
      # The store directory, on the volume
      TOPGUN_DATA_DIR: /data/topgun-data
      # The store file of earlier versions: migrated into TOPGUN_DATA_DIR once,
      # then replaced by a small marker file
      TOPGUN_REDB_PATH: /data/topgun.redb
      # The write-ahead log must sit on the same volume as the store directory
      TOPGUN_WAL_DIR: /data/topgun-wal
      # Optional: enable auth
      JWT_SECRET: your-dev-secret
    volumes:
      - topgun-data:/data
    restart: unless-stopped

volumes:
  topgun-data:
terminal
docker-compose up -d

Backup and restore

The data is two directories on the volume: the data directory (TOPGUN_DATA_DIR, /data/topgun-data in the image) and the write-ahead log directory (TOPGUN_WAL_DIR, /data/topgun-wal in the image). Acknowledged writes that are not yet in the data directory live in the WAL directory, so a backup needs both.

  • Back up: stop the server (SIGTERM; it drains its write buffer and syncs the log, then exits 0), then copy the whole data directory recursively and the WAL directory.
  • Restore: with the server stopped, put both back, then start it.
  • Not supported: a copy of a running server’s directory.

Upgrading a volume from an earlier version

Earlier versions kept the store as one file, /data/topgun.redb. The first start of this version migrates it into /data/topgun-data by itself, keeps the old file as /data/topgun.redb.pre-migration, and leaves a small marker file at /data/topgun.redb so that an older image refuses to start on the volume instead of serving an empty store. Copy /data/topgun.redb off the volume before upgrading, and make sure the volume has 1.5 × that file’s size plus 64 MiB free. Rolling back is only a return to the kept file and loses every write acknowledged since the upgrade; see Storage backends.

Kubernetes

For production Kubernetes deployments, use a StatefulSet so pods get stable network identities. Keep /data on a persistent volume (volumeClaimTemplates below), and store JWT_SECRET and admin credentials in a Kubernetes Secret.

Secrets

k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: topgun-secrets
  namespace: topgun
type: Opaque
stringData:
  jwt-secret: "your-production-secret-min-32-chars"
  admin-username: "admin"
  admin-password: "your-admin-password"

StatefulSet + Service

k8s/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: topgun-server
  namespace: topgun
spec:
  serviceName: topgun-server
  replicas: 1
  selector:
    matchLabels:
      app: topgun-server
  template:
    metadata:
      labels:
        app: topgun-server
    spec:
      containers:
        - name: topgun-server
          image: topgun-server:latest
          ports:
            - containerPort: 8080
              name: ws
          env:
            - name: RUST_LOG
              value: "topgun_server=info"
            - name: TOPGUN_BIND_ADDR
              value: "0.0.0.0"
            - name: TOPGUN_DATA_DIR
              value: "/data/topgun-data"
            - name: TOPGUN_REDB_PATH
              value: "/data/topgun.redb"
            - name: TOPGUN_WAL_DIR
              value: "/data/topgun-wal"
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: topgun-secrets
                  key: jwt-secret
            - name: TOPGUN_ADMIN_USERNAME
              valueFrom:
                secretKeyRef:
                  name: topgun-secrets
                  key: admin-username
            - name: TOPGUN_ADMIN_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: topgun-secrets
                  key: admin-password
            - name: TOPGUN_MAX_RAM_MB
              value: "1024"
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "2Gi"
              cpu: "2"
          volumeMounts:
            - name: data
              mountPath: /data
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            periodSeconds: 10
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
  name: topgun-server
  namespace: topgun
spec:
  selector:
    app: topgun-server
  ports:
    - port: 8080
      targetPort: 8080
      name: ws
  type: ClusterIP

All env entries above correspond to environment variables the binary actually reads (std::env::var(...) or clap #[arg(env)]). For the complete variable list and source-file references, see Server & CLI reference.

TLS

The server binary does not read any TOPGUN_TLS_* environment variables today.

The binary reads zero TOPGUN_TLS_* env vars (verified: 22 std::env::var() call sites, none matching TOPGUN_TLS). TLS is configured programmatically via TlsConfig when embedding the server in a Rust host process. See Server & CLI reference — embed API for the programmatic approach.

For standard production deployments, terminate TLS at a reverse proxy in front of the server:

  • nginx — proxy_pass http://topgun-server:8080 with ssl on on the nginx listener
  • Caddy — reverse_proxy topgun-server:8080 under a tls directive
  • AWS ALB — HTTPS listener with target group pointing at port 8080; ALB handles TLS offload

WebSocket connections (ws://) are proxied as-is; the proxy upgrades wss:// externally.

Serverless

Serverless edge deployment is not supported for the TopGun server.

The Rust server is a long-running process that maintains in-memory CRDT state, WebSocket connections, and cluster membership. It does not support Vercel Edge Functions, AWS Lambda, or Cloudflare Workers.

Deploy TopGun as a persistent service (Docker, Kubernetes, or any platform supporting long-running processes) and connect from your edge functions using the TopGun client SDK:

Edge function connecting to TopGun server
import { HttpSyncProvider, SyncEngine } from '@topgunbuild/client';
import { HLC } from '@topgunbuild/core';
import { IDBAdapter } from '@topgunbuild/adapters';

const hlc = new HLC('client-1');
const provider = new HttpSyncProvider({
  url: 'https://your-topgun-server.example.com',
  clientId: 'client-1',
  hlc,
  authToken: 'your-jwt-token',
  syncMaps: ['todos'],
});

const engine = new SyncEngine({
  nodeId: 'client-1',
  connectionProvider: provider,
  storageAdapter: new IDBAdapter(),
});

HttpSyncProvider identifies itself to the client as an HTTP transport, so there is no setting to add for it; see custom connection providers in the client reference if you write a provider of your own.