Skip to Content

Deployment

Deploy your Gluonic sync server to production.

Overview

Gluonic servers are standard Fastify/Express apps and can be deployed anywhere Node.js runs:

  • AWS (ECS, Lambda, EC2)
  • Vercel/Netlify (serverless)
  • Digital Ocean
  • Heroku
  • Self-hosted

Prerequisites

  • AWS account
  • Docker installed
  • AWS Copilot CLI

Deployment Steps

# 1. Initialize Copilot copilot init # 2. Create environment copilot env init --name production # 3. Deploy service copilot deploy --name sync-api --env production

Environment Variables

copilot/sync-api/manifest.yml
variables: PORT: 3000 NODE_ENV: production secrets: DATABASE_URL: /gluonic/production/database-url JWT_SECRET: /gluonic/production/jwt-secret REDIS_ENDPOINT: !GetAtt RedisCluster.PrimaryEndpoint.Address REDIS_PORT: !GetAtt RedisCluster.PrimaryEndpoint.Port

Redis (ElastiCache)

# Add to copilot/environments/production/manifest.yml redis: cluster: engine: redis engineVersion: 7.0 instanceClass: cache.t4g.micro

Vercel (Serverless)

Works with serverless, but WebSocket requires upgrade:

api/sync/[...path].ts
import { SyncServer } from '@gluonic/server' import { PrismaAdapter } from '@gluonic/server-prisma' let server export default async function handler(req, res) { if (!server) { const database = PrismaAdapter({ prisma, models }) server = SyncServer({ database, auth }) } return server.fastify.ready().then(() => { server.fastify.routing(req, res) }) }

Limitations:

  • ❌ WebSocket not supported on Vercel (use Pusher/Ably instead)
  • ⚠️ Cold starts can cause issues
  • ✅ Works for HTTP endpoints (bootstrap, delta, tx)

Docker

Dockerfile

FROM node:20-alpine WORKDIR /app # Install dependencies COPY package.json pnpm-lock.yaml ./ RUN npm install -g pnpm && pnpm install --frozen-lockfile # Copy source COPY . . # Build RUN pnpm build # Expose port EXPOSE 3000 # Start CMD ["node", "dist/index.js"]

Docker Compose

docker-compose.yml
services: sync-api: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgresql://user:pass@db:5432/gluonic - REDIS_URL=redis://redis:6379 - JWT_SECRET=your-secret-key depends_on: - db - redis db: image: postgres:16-alpine environment: - POSTGRES_PASSWORD=password - POSTGRES_DB=gluonic volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: - redisdata:/data volumes: pgdata: redisdata:

Deploy

docker compose up -d

Environment Configuration

Required Variables

# Database DATABASE_URL="postgresql://..." # JWT JWT_SECRET="your-super-secret-key-change-me" # Server PORT=3000 NODE_ENV=production

Optional Variables

# Redis (for real-time) REDIS_URL="redis://localhost:6379" # Or AWS ElastiCache format: REDIS_ENDPOINT="my-cluster.abc123.use1.cache.amazonaws.com" REDIS_PORT="6379" REDIS_AUTH_TOKEN="optional-auth-token" # Snapshots SNAPSHOT_BUCKET="my-app-snapshots" AWS_REGION="us-east-1" # Performance SYNC_MAX_DELTA=5000 SYNC_DELTA_MIN_INTERVAL_MS=100 SYNC_ACTION_RETENTION_DAYS=30

Health Checks

Add health check endpoint:

import { SyncServer } from '@gluonic/server' const server = SyncServer({ database, auth }) server.fastify.get('/health', async (req, reply) => { // Check database try { await prisma.$queryRaw`SELECT 1` } catch (error) { return reply.status(500).send({ status: 'unhealthy', reason: 'database' }) } // Check Redis (if using) if (redisClient) { try { await redisClient.ping() } catch (error) { return reply.status(500).send({ status: 'unhealthy', reason: 'redis' }) } } reply.send({ status: 'healthy' }) })

Monitoring

Metrics Endpoint

GET /sync/v1/metrics { "bootstrapCount": 150, "bootstrapMsTotal": 45000, "deltaCount": 5000, "deltaMsTotal": 15000, "deltaFramesTotal": 25000, "wsBroadcastedFrames": 50000, "wsConnections": 42 }

CloudWatch (AWS)

import { CloudWatch } from '@aws-sdk/client-cloudwatch' const cloudwatch = new CloudWatch({ region: 'us-east-1' }) // Log custom metrics await cloudwatch.putMetricData({ Namespace: 'Gluonic', MetricData: [{ MetricName: 'WebSocketConnections', Value: metrics.wsConnections, Unit: 'Count' }] })

Scaling

Horizontal Scaling

With Redis broadcaster:

┌────────────┐ │ Redis │ │ Pub/Sub │ └──────┬─────┘ ┌────────────────┼────────────────┐ │ │ │ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ └───┬───┘ └───┬───┘ └───┬───┘ │ │ │ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ │Client │ │Client │ │Client │ │ A,B │ │ C,D │ │ E,F │ └───────┘ └───────┘ └───────┘

All clients receive updates regardless of which pod they’re connected to!

Load Balancing

# Nginx config upstream sync_backend { server pod1.example.com:3000; server pod2.example.com:3000; server pod3.example.com:3000; } server { listen 443 ssl; server_name api.example.com; location /sync/v1/ { proxy_pass http://sync_backend; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } }

Security

Production Checklist

  • Use HTTPS/WSS (never HTTP/WS)
  • Strong JWT secrets (32+ characters, rotated)
  • Rate limiting on all endpoints
  • CORS configured (not * in production)
  • Redis secured (password, TLS)
  • Database secured (SSL, least-privilege user)
  • Secrets in environment variables (never in code)
  • Regular security audits

CORS Configuration

import cors from '@fastify/cors' server.fastify.register(cors, { origin: [ 'https://app.example.com', 'https://admin.example.com' ], credentials: true })

Database Migrations

# Run migrations before deployment pnpm prisma migrate deploy # Or in Docker entrypoint: #!/bin/sh npx prisma migrate deploy node dist/index.js

Next Steps

Last updated on