This guide covers deploying DatomniX to production using Railway (backend) and Vercel (frontend).
---
---
# Install Railway CLI
npm install -g @railway/cli
# Login to Railway
railway login
# Initialize project
railway initIn the Railway dashboard:
1. Add PostgreSQL Database
- Click "New" → "Database" → "PostgreSQL"
- Note the connection string
2. Add Redis
- Click "New" → "Database" → "Redis"
- Note the Redis URL
3. Deploy API Service
- Click "New" → "GitHub Repo"
- Select your DatomniX repository
- Set root directory to apps/api
Add these variables to your API service in Railway:
# Database (auto-generated by Railway)
DATABASE_URL=${{Postgres.DATABASE_URL}}
# Redis (auto-generated by Railway)
REDIS_HOST=${{Redis.REDIS_HOST}}
REDIS_PORT=${{Redis.REDIS_PORT}}
REDIS_PASSWORD=${{Redis.REDIS_PASSWORD}}
# JWT Secret (for authentication)
JWT_SECRET=your-secret-key-change-in-production
# App Config
PORT=3000
NODE_ENV=production
API_URL=${{RAILWAY_PUBLIC_DOMAIN}}
FRONTEND_URL=https://your-datomnix-domain.vercel.app
# OAuth Integrations (Required for ads platform connections)
META_APP_ID=your-meta-app-id
META_APP_SECRET=your-meta-app-secret
# Optional OAuth providers
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
TIKTOK_APP_ID=your-tiktok-app-id
TIKTOK_APP_SECRET=your-tiktok-app-secret
# Encryption (Required for storing OAuth tokens)
ENCRYPTION_KEY=your-256-bit-encryption-key
# Optional: Rocky Integration (for existing Rocky client)
# These are only needed if Rocky brand uses custom integration
# ROCKY_API_URL=https://rocky-be-production.up.railway.app/api/v1
# ROCKY_JWT_SECRET=your-rocky-jwt-secret
# Tracking domains (first-party CNAMEs via Cloudflare for SaaS). Both the token
# and zone id are REQUIRED for a brand to be able to provision one: unset
# either and the API refuses create requests instead of writing a domain row
# with nothing behind it. Set these on the API service specifically, not just
# locally, or the feature is enabled in development and silently dead in
# production, which is exactly how it shipped without them the first time.
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_ZONE_ID=your-datomnix-com-zone-id
CLOUDFLARE_TRACKING_WORKER_SCRIPT_NAME=datomnix-tracking-edge
TRACKING_SHARED_HOST=api.datomnix.com
TRACKING_CNAME_TARGET=dx.datomnix.com
# Polling
POLLING_INTERVAL_MS=60000Create apps/api/railway.json:
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS",
"buildCommand": "npm install && npm run build && npx prisma generate"
},
"deploy": {
"startCommand": "npx prisma migrate deploy && npm run start:prod",
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}# From project root
cd apps/api
railway upOr push to GitHub to trigger automatic deployment.
After first deployment:
railway run npx prisma migrate deployAfter deployment, configure OAuth callbacks in your provider dashboards:
Meta (Facebook) Developer Console:
https://your-api.railway.app/integrations/oauth/meta/callback
http://localhost:3001/integrations/oauth/meta/callbackGoogle Cloud Console:
https://your-api.railway.app/integrations/oauth/google/callback
http://localhost:3001/integrations/oauth/google/callbackTikTok Developer Portal:
https://your-api.railway.app/integrations/oauth/tiktok/callback
http://localhost:3001/integrations/oauth/tiktok/callbackImportant Notes:
/api/v1 prefix---
1. Go to https://vercel.com/new
2. Import your GitHub repository
3. Configure project:
- Framework Preset: Next.js
- Root Directory: apps/web
- Build Command: npm run build
- Output Directory: .next
Add in Vercel project settings:
NEXT_PUBLIC_API_URL=https://your-api-domain.railway.app/api/v1Vercel will automatically deploy on:
---
1. Create Production Compose File
docker-compose.prod.yml:
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: datomnix
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
restart: unless-stopped
api:
build:
context: .
dockerfile: apps/api/Dockerfile
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/datomnix
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
ROCKY_API_URL: ${ROCKY_API_URL}
ROCKY_JWT_SECRET: ${ROCKY_JWT_SECRET}
PORT: 3001
NODE_ENV: production
ports:
- "3001:3001"
depends_on:
- postgres
- redis
restart: unless-stopped
web:
build:
context: .
dockerfile: apps/web/Dockerfile
environment:
NEXT_PUBLIC_API_URL: http://api:3001/api/v1
ports:
- "3000:3000"
depends_on:
- api
restart: unless-stopped
volumes:
postgres_data:
redis_data:2. Create Dockerfiles
apps/api/Dockerfile:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY apps/api/package*.json ./apps/api/
COPY packages/ ./packages/
RUN npm install
COPY apps/api/ ./apps/api/
COPY turbo.json ./
RUN npm run build --workspace=apps/api
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/prisma ./prisma
COPY --from=builder /app/apps/api/package*.json ./
RUN npx prisma generate
EXPOSE 3001
CMD ["npm", "run", "start:prod"]apps/web/Dockerfile:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY apps/web/package*.json ./apps/web/
COPY packages/ ./packages/
RUN npm install
COPY apps/web/ ./apps/web/
COPY turbo.json ./
RUN npm run build --workspace=apps/web
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/apps/web/.next ./.next
COPY --from=builder /app/apps/web/public ./public
COPY --from=builder /app/apps/web/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]3. Deploy
# Create .env file with secrets
cp .env.example .env.prod
# Edit .env.prod with production values
# Build and start
docker-compose -f docker-compose.prod.yml --env-file .env.prod up -d
# Run migrations
docker-compose -f docker-compose.prod.yml exec api npx prisma migrate deploySee k8s/ directory for Kubernetes manifests (to be added).
---
1. Never drop columns/tables in production
2. Use safe migrations:
- Add new columns as nullable first
- Backfill data
- Make non-nullable later
3. Test migrations on staging first
# Railway
railway run npx prisma migrate deploy
# Docker
docker-compose exec api npx prisma migrate deploy
# Manual
cd apps/api && npx prisma migrate deployIf a migration fails:
# Revert to previous migration
npx prisma migrate resolve --rolled-back MIGRATION_NAME
# Deploy previous version
git revert HEAD
git push---
NODE_ENV=development
DATABASE_URL=postgresql://datomnix:datomnix@localhost:5432/datomnix
REDIS_HOST=localhost
POLLING_INTERVAL_MS=60000NODE_ENV=staging
DATABASE_URL=<staging-db-url>
REDIS_HOST=<staging-redis>
POLLING_INTERVAL_MS=30000 # More frequent for testingNODE_ENV=production
DATABASE_URL=<production-db-url>
REDIS_HOST=<production-redis>
POLLING_INTERVAL_MS=60000---
View logs in Railway dashboard:
View in Vercel dashboard:
Recommended integrations:
1. Sentry (Error tracking)
npm install @sentry/node @sentry/nestjs2. Datadog (APM)
npm install dd-trace3. LogDNA (Log aggregation)
- Add LogDNA drain in Railway
---
1. Connection Pooling
// In DATABASE_URL
?connection_limit=10&pool_timeout=202. Redis Caching
- Cache frequently accessed data
- Set reasonable TTLs
3. Query Optimization
- Add indexes for common queries
- Use prisma.$queryRaw for complex queries
1. Static Generation
- Use generateStaticParams for static pages
2. Image Optimization
- Use Next.js component
3. Code Splitting
- Dynamic imports for heavy components
---
#
#
# Create backup
docker exec datomnix-postgres pg_dump -U datomnix datomnix > backup.sql
# Restore backup
docker exec -i datomnix-postgres psql -U datomnix datomnix < backup.sql---
1. Database Corruption
- Restore from latest backup
- Replay transaction logs
- Verify data integrity
2. Service Outage
- Railway: Auto-restart (configured)
- Manual: railway restart
3. Data Loss
- Restore from backup
- Re-ingest last 24h from Rocky BE
---
1. Upgrade plan in Railway dashboard
2. Increase database resources
3. Monitor performance metrics
1. API Instances
- Deploy multiple Railway services
- Add load balancer (Cloudflare/AWS ALB)
2. Consumer Scaling
- Redis Streams supports multiple consumers
- Set CONSUMER_COUNT environment variable
3. Database Scaling
- Read replicas for queries
- Connection pooling (PgBouncer)
---
npm audit)---
# Check logs
railway logs
# Common issues:
# 1. Missing environment variables
# 2. Database connection failed
# 3. Redis unavailable# Reset migration state
npx prisma migrate resolve --applied MIGRATION_NAME
# Or force reset (development only!)
npx prisma migrate reset# Check API metrics in Railway
# Likely causes:
# 1. Memory leak in long-running jobs
# 2. Large event payloads
# 3. Insufficient connection pooling# Check Redis stream
redis-cli XINFO STREAM datomnix:events
# Check consumer group
redis-cli XINFO GROUPS datomnix:events
# Check pending messages
redis-cli XPENDING datomnix:events datomnix-attribution---
For deployment issues: