Deployment
Deploy your Gluonic sync server to production.
API Reference: SyncServer · PrismaAdapter · RedisBroadcaster
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
AWS ECS (Recommended for Production)
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 productionEnvironment 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.PortRedis (ElastiCache)
# Add to copilot/environments/production/manifest.yml
redis:
cluster:
engine: redis
engineVersion: 7.0
instanceClass: cache.t4g.microVercel (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 -dEnvironment Configuration
Required Variables
# Database
DATABASE_URL="postgresql://..."
# JWT
JWT_SECRET="your-super-secret-key-change-me"
# Server
PORT=3000
NODE_ENV=productionOptional 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=30Health 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.jsNext Steps
- Performance - Optimize for scale
- Monitoring - Observability
- Security - Harden production
Last updated on