Self-host the server
Run the TopGun server on your own infrastructure using Docker, Docker Compose, or Kubernetes. By default the server uses an embedded redb database — no Postgres or external dependency required. Switch to PostgreSQL when you need a shared production backend.
Building the image
# 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 redb (default — no Postgres required)
# Run with embedded redb backend (default — no Postgres required)
docker run -d \
--name topgun-server \
-p 8080:8080 \
-e RUST_LOG=topgun_server=info \
-v topgun-data:/data \
-e TOPGUN_REDB_PATH=/data/topgun.redb \
topgun-server Run with PostgreSQL (opt-in)
# Run with PostgreSQL backend (opt-in)
docker run -d \
--name topgun-server \
-p 8080:8080 \
-e RUST_LOG=topgun_server=info \
-e STORAGE_BACKEND=postgres \
-e DATABASE_URL=postgres://user:pass@db-host:5432/myapp \
-e JWT_SECRET=your-production-secret \
topgun-server Docker Compose
redb default (recommended for single-node)
version: '3.8'
services:
server:
build:
context: .
dockerfile: deploy/Dockerfile.server
container_name: topgun-server
ports:
- "8080:8080"
environment:
RUST_LOG: topgun_server=info
# redb is the default; TOPGUN_REDB_PATH sets the file location
TOPGUN_REDB_PATH: /data/topgun.redb
# Optional: enable auth
JWT_SECRET: your-dev-secret
volumes:
- topgun-data:/data
restart: unless-stopped
volumes:
topgun-data: docker-compose up -d With PostgreSQL (opt-in)
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: topgun-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres_password
POSTGRES_DB: topgun
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
server:
build:
context: .
dockerfile: deploy/Dockerfile.server
container_name: topgun-server
depends_on:
postgres:
condition: service_healthy
ports:
- "8080:8080"
environment:
RUST_LOG: topgun_server=info
STORAGE_BACKEND: postgres
DATABASE_URL: postgres://postgres:postgres_password@postgres:5432/topgun
JWT_SECRET: your-production-secret
TOPGUN_BIND_ADDR: "0.0.0.0"
restart: unless-stopped
volumes:
postgres_data: Kubernetes
For production Kubernetes deployments, use a StatefulSet so pods get stable network identities. Store DATABASE_URL, JWT_SECRET, and admin credentials in a Kubernetes Secret.
Secrets
apiVersion: v1
kind: Secret
metadata:
name: topgun-secrets
namespace: topgun
type: Opaque
stringData:
database-url: "postgres://user:pass@postgres-host:5432/topgun"
jwt-secret: "your-production-secret-min-32-chars"
admin-username: "admin"
admin-password: "your-admin-password" StatefulSet + Service
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: STORAGE_BACKEND
value: "postgres"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: topgun-secrets
key: database-url
- 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"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
---
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
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:8080withssl onon the nginx listener - Caddy —
reverse_proxy topgun-server:8080under atlsdirective - 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
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:
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(),
});