DocsGuidesDeployment

Deployment Guide

Learn how to deploy the TopGun server to production using Docker, Docker Compose, or Kubernetes.

Docker

TopGun provides a standardized Dockerfile for building the server. It uses a multi-stage build process to ensure a small image size.

Building the Image

terminal
# 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

For local development or simple deployments, you can use Docker Compose to run the TopGun Server alongside a PostgreSQL database.
docker-compose.yml
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

terminal
docker-compose up -d

Kubernetes (Helm)

For production Kubernetes clusters, a Helm chart is provided in 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 API
  • templates/service-headless.yaml- Headless service for internal cluster discovery
  • values.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

Recommended configuration for production workloads with high connection counts.
Production Server Setup
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

SettingValuePurpose
eventQueueCapacity50000Handle traffic bursts without dropping events
backpressureSyncFrequency100Balance throughput and latency
writeCoalescingMaxDelayMs5Batch writes with 5ms max delay
maxConnectionsPerSecond200DDoS protection

Performance Tuning Guide

For detailed tuning guidance including high-throughput and low-latency configurations, see the Performance Tuning Guide.

TLS Configuration

For production deployments, enable TLS to encrypt all client and cluster communications.

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

docker-compose.tls.yml
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:

terminal
# 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

Store TLS certificates securely using Kubernetes Secrets.
k8s/tls-secret.yaml
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

k8s/deployment.yaml
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

Deploy TopGun to serverless platforms using the HTTP sync protocol (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
// 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
// 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
// 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.