Deployment Guide
Learn how to deploy the TopGun server to production using Docker, Docker Compose, or Kubernetes.
Docker
Building the Image
# From the root of the repository
docker build -t topgun-server -f deploy/Dockerfile.server . The Dockerfile handles:
- Copying workspace package definitions
- Installing dependencies via
pnpm install - Building all packages (core, server)
- Exposing port
8080 - Starting the server via
node packages/server/dist/start-server.js
Docker Compose
version: '3.8'
services:
# PostgreSQL Database
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
# TopGun Server
server:
build:
context: .
dockerfile: deploy/Dockerfile.server
container_name: topgun-server
depends_on:
postgres:
condition: service_healthy
ports:
- "8080:8080"
environment:
NODE_ENV: production
DATABASE_URL: postgres://postgres:postgres_password@postgres:5432/topgun
restart: unless-stopped
volumes:
postgres_data: Running with Compose
docker-compose up -d Kubernetes (Helm)
deploy/k8s/chart.Chart Structure
templates/statefulset.yaml- Manages the server pods (StatefulSet is recommended for stable network IDs)templates/service.yaml- Exposes the HTTP/WebSocket APItemplates/service-headless.yaml- Headless service for internal cluster discoveryvalues.yaml- Default configuration values
Deploying with Helm allows easy scaling of the server replicas. TopGun nodes automatically discover each other when running in the same cluster (configured via cluster.discovery).
Production Performance Settings
import { ServerCoordinator } from '@topgunbuild/server';
import { PostgresAdapter } from '@topgunbuild/server/storage';
const server = new ServerCoordinator({
port: 8080,
nodeId: process.env.NODE_ID || 'prod-node-1',
storage: new PostgresAdapter(process.env.DATABASE_URL),
// Production performance settings
wsBacklog: 511,
maxConnections: 10000,
eventQueueCapacity: 50000,
eventStripeCount: 4,
// Backpressure tuning
backpressureEnabled: true,
backpressureSyncFrequency: 100,
backpressureMaxPending: 2000,
// Write coalescing
writeCoalescingEnabled: true,
writeCoalescingMaxDelayMs: 5,
// Rate limiting for DDoS protection
rateLimitingEnabled: true,
maxConnectionsPerSecond: 200,
maxPendingConnections: 2000,
});
await server.ready();
console.log(`Server running on port ${server.port}`); Key Settings Explained
| Setting | Value | Purpose |
|---|---|---|
eventQueueCapacity | 50000 | Handle traffic bursts without dropping events |
backpressureSyncFrequency | 100 | Balance throughput and latency |
writeCoalescingMaxDelayMs | 5 | Batch writes with 5ms max delay |
maxConnectionsPerSecond | 200 | DDoS protection |
Performance Tuning Guide
For detailed tuning guidance including high-throughput and low-latency configurations, see the Performance Tuning Guide.TLS Configuration
Security Requirement
Always enable TLS in production. Without TLS, all data (including authentication tokens) is transmitted in plaintext and can be intercepted.Docker Compose with TLS
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: topgun-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres_password
POSTGRES_DB: topgun
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:
- "443:443" # HTTPS/WSS
- "9443:9443" # Cluster TLS
environment:
NODE_ENV: production
TOPGUN_PORT: 443
TOPGUN_CLUSTER_PORT: 9443
DATABASE_URL: postgres://postgres:postgres_password@postgres:5432/topgun
# TLS Configuration
TOPGUN_TLS_ENABLED: "true"
TOPGUN_TLS_CERT_PATH: /etc/topgun/tls/server.crt
TOPGUN_TLS_KEY_PATH: /etc/topgun/tls/server.key
TOPGUN_TLS_MIN_VERSION: TLSv1.3
# Cluster TLS (mTLS)
TOPGUN_CLUSTER_TLS_ENABLED: "true"
TOPGUN_CLUSTER_TLS_CERT_PATH: /etc/topgun/tls/cluster.crt
TOPGUN_CLUSTER_TLS_KEY_PATH: /etc/topgun/tls/cluster.key
TOPGUN_CLUSTER_TLS_CA_PATH: /etc/topgun/tls/ca.crt
TOPGUN_CLUSTER_MTLS: "true"
volumes:
- ./certs:/etc/topgun/tls:ro
restart: unless-stopped
volumes:
postgres_data: Before starting, create your certificates in the ./certs directory:
# Create certs directory
mkdir -p certs
# Copy your certificates
cp /path/to/server.crt certs/
cp /path/to/server.key certs/
cp /path/to/ca.crt certs/
# For cluster mTLS (separate certs)
cp /path/to/cluster.crt certs/
cp /path/to/cluster.key certs/
# Start with TLS
docker-compose -f docker-compose.tls.yml up -d Kubernetes TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: topgun-tls
namespace: topgun
type: kubernetes.io/tls
data:
# Base64-encoded certificate and key
tls.crt: LS0tLS1CRUdJTi... # base64 -w0 server.crt
tls.key: LS0tLS1CRUdJTi... # base64 -w0 server.key
ca.crt: LS0tLS1CRUdJTi... # base64 -w0 ca.crt
---
# Create secret from files:
# kubectl create secret generic topgun-tls \
# --from-file=tls.crt=./server.crt \
# --from-file=tls.key=./server.key \
# --from-file=ca.crt=./ca.crt \
# -n topgun Kubernetes Deployment with TLS
apiVersion: apps/v1
kind: Deployment
metadata:
name: topgun
namespace: topgun
spec:
replicas: 3
selector:
matchLabels:
app: topgun
template:
metadata:
labels:
app: topgun
spec:
containers:
- name: topgun
image: topgun:latest
ports:
- containerPort: 443
name: https
- containerPort: 9443
name: cluster
env:
- name: TOPGUN_PORT
value: "443"
- name: TOPGUN_CLUSTER_PORT
value: "9443"
- name: TOPGUN_TLS_ENABLED
value: "true"
- name: TOPGUN_TLS_CERT_PATH
value: "/etc/topgun/tls/tls.crt"
- name: TOPGUN_TLS_KEY_PATH
value: "/etc/topgun/tls/tls.key"
- name: TOPGUN_TLS_MIN_VERSION
value: "TLSv1.3"
- name: TOPGUN_CLUSTER_TLS_ENABLED
value: "true"
- name: TOPGUN_CLUSTER_TLS_CA_PATH
value: "/etc/topgun/tls/ca.crt"
- name: TOPGUN_CLUSTER_MTLS
value: "true"
volumeMounts:
- name: tls-certs
mountPath: /etc/topgun/tls
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: topgun-tls cert-manager Integration
For automatic certificate management, consider using cert-manager with Let’s Encrypt or your internal CA. See the Security Guide for detailed setup instructions.Serverless Deployment
POST /sync endpoint).TopGun supports serverless deployment via its HTTP sync protocol. The server exposes the POST /sync endpoint automatically when using ServerFactory or ServerCoordinator. For the endpoint’s request/response format, see the Server API reference.
For serverless environments where you want a lightweight handler without the full ServerCoordinator (which initializes WebSocket servers, cluster managers, and worker pools), HttpSyncHandler can be used directly. It provides the stateless handleSyncRequest() method designed for serverless cold-start environments.
Import Limitation
HttpSyncHandler is not currently exported from the @topgunbuild/server package. The main entry point (@topgunbuild/server) does not re-export from the coordinator module, and there is no @topgunbuild/server/coordinator sub-path export. The package.json exports map only contains ”.”, and the tsup build only produces index.js/index.mjs entry points.
A code change is needed to make HttpSyncHandler independently importable: add ”./coordinator” to the package’s exports map and add src/coordinator/index.ts as a tsup entry point.
The examples below show the intended usage pattern once this export is available.
Vercel Edge Function
// app/api/sync/route.ts (Next.js App Router)
import { HttpSyncHandler } from '@topgunbuild/server/coordinator';
import { HLC } from '@topgunbuild/core';
// Module-scoped singleton -- reused across warm invocations
let handler: HttpSyncHandler | null = null;
function getHandler(): HttpSyncHandler {
if (!handler) {
// HttpSyncHandler requires server dependencies via HttpSyncHandlerConfig:
// authHandler, operationHandler, storageManager,
// queryConversionHandler, searchCoordinator, hlc, securityManager
handler = new HttpSyncHandler({
hlc: new HLC('vercel-edge-1'),
authHandler, // IAuthHandler -- verify JWT tokens
operationHandler, // IOperationHandler -- apply CRDT operations
storageManager, // IStorageManager -- access LWWMap data
queryConversionHandler, // IQueryConversionHandler
searchCoordinator, // search interface
securityManager, // permission checks
});
}
return handler;
}
export async function POST(request: Request) {
const authHeader = request.headers.get('authorization');
const authToken = authHeader?.replace('Bearer ', '') ?? '';
const body = await request.json();
const result = await getHandler().handleSyncRequest(body, authToken);
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} AWS Lambda
// handler.ts (AWS Lambda)
import { HttpSyncHandler } from '@topgunbuild/server/coordinator';
import { HLC } from '@topgunbuild/core';
let handler: HttpSyncHandler | null = null;
function getHandler(): HttpSyncHandler {
if (!handler) {
handler = new HttpSyncHandler({
hlc: new HLC('lambda-1'),
authHandler, operationHandler, storageManager,
queryConversionHandler, searchCoordinator, securityManager,
});
}
return handler;
}
export const syncHandler = async (event: any) => {
const authToken = (event.headers?.authorization ?? '').replace('Bearer ', '');
const body = JSON.parse(event.body ?? '{}');
const result = await getHandler().handleSyncRequest(body, authToken);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result),
};
}; Cloudflare Worker
// src/index.ts (Cloudflare Worker)
import { HttpSyncHandler } from '@topgunbuild/server/coordinator';
import { HLC } from '@topgunbuild/core';
let handler: HttpSyncHandler | null = null;
function getHandler(): HttpSyncHandler {
if (!handler) {
handler = new HttpSyncHandler({
hlc: new HLC('cf-worker-1'),
authHandler, operationHandler, storageManager,
queryConversionHandler, searchCoordinator, securityManager,
});
}
return handler;
}
export default {
async fetch(request: Request): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const authToken = (request.headers.get('authorization') ?? '').replace('Bearer ', '');
const body = await request.json();
const result = await getHandler().handleSyncRequest(body, authToken);
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
},
}; Client Configuration for Serverless
To connect a client to a serverless TopGun deployment, use HttpSyncProvider or AutoConnectionProvider with SyncEngine. These providers implement the IConnectionProvider interface and are used with SyncEngine directly — not with TopGunClient (which only accepts serverUrl and cluster config options).
Using HttpSyncProvider
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-api.vercel.app',
clientId: 'client-1',
hlc,
authToken: 'your-jwt-token',
syncMaps: ['todos'],
});
const engine = new SyncEngine({
nodeId: 'client-1',
connectionProvider: provider,
storageAdapter: new IDBAdapter(),
}); Using AutoConnectionProvider
For environments where you are unsure if WebSocket is available, AutoConnectionProvider tries WebSocket first and falls back to HTTP:
import { AutoConnectionProvider, SyncEngine } from '@topgunbuild/client';
import { HLC } from '@topgunbuild/core';
import { IDBAdapter } from '@topgunbuild/adapters';
const hlc = new HLC('client-1');
const provider = new AutoConnectionProvider({
url: 'https://your-api.vercel.app',
clientId: 'client-1',
hlc,
authToken: 'your-jwt-token',
maxWsAttempts: 3,
syncMaps: ['todos'],
});
const engine = new SyncEngine({
nodeId: 'client-1',
connectionProvider: provider,
storageAdapter: new IDBAdapter(),
}); Provider Configuration Reference
For the full list of configuration options for HttpSyncProvider and
AutoConnectionProvider,
see the Client API reference.