DubheSuiProduction Deployment

Production Deployment Guide

This guide covers deploying Dubhe applications to production environments (testnet/mainnet) with independent service deployment architecture. Unlike the localnet quick start, production deployment requires separating services for scalability, security, and maintainability.

Architecture Overview

A production Dubhe deployment consists of several independent services:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Frontend      │    │   Database      │    │   Blockchain    │
│   (Vercel)      │    │ (PostgreSQL)    │    │ (Testnet/Main)  │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘

         ┌─────────────────┐    ┌─────────────────┐
         │  GraphQL Server │    │    Indexer     │
         │   (Node.js)     │    │    (Rust)      │
         └─────────────────┘    └─────────────────┘

Prerequisites

Before starting production deployment, ensure you have:

  1. Deployed contracts on your target network (testnet/mainnet)
  2. PostgreSQL database instance (cloud or self-hosted)
  3. Domain names for your services (optional but recommended)
  4. Environment variables properly configured
  5. Sufficient tokens for transaction fees

Step 1: Contract Deployment

Deploy to Testnet

First, ensure you have test tokens and deploy your contracts:

# Generate account if needed
dubhe generate-key
 
# Check balance on testnet
dubhe check-balance --network testnet
 
# Get test tokens if needed
dubhe faucet --network testnet
 
# Deploy contracts to testnet
dubhe publish --network testnet --config-path dubhe.config.ts
 
# Store configuration for other services
dubhe config-store --network testnet --output-ts-path ./deployment.ts

Deploy to Mainnet

For mainnet deployment, ensure you have sufficient SUI tokens:

# Check balance on mainnet
dubhe check-balance --network mainnet
 
# Deploy contracts to mainnet
dubhe publish --network mainnet --config-path dubhe.config.ts --gas-budget 1000000000
 
# Store configuration for production
dubhe config-store --network mainnet --output-ts-path ./deployment.ts

Environment Configuration

After deployment, update your environment variables:

# .env.production
NETWORK=testnet  # or mainnet
PACKAGE_ID=0x...  # From deployment output
DUBHE_SCHEMA_ID=0x...  # From deployment output
PRIVATE_KEY=your_private_key

Step 2: Database Setup

PostgreSQL Cloud Setup

For production, use a managed PostgreSQL service:

AWS RDS:

# Create RDS instance
aws rds create-db-instance \
  --db-instance-identifier dubhe-prod \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --master-username dubhe \
  --master-user-password your_password \
  --allocated-storage 20

Google Cloud SQL:

# Create Cloud SQL instance
gcloud sql instances create dubhe-prod \
  --database-version=POSTGRES_15 \
  --tier=db-f1-micro \
  --region=us-central1

Digital Ocean:

# Create managed database
doctl databases create dubhe-prod \
  --engine postgres \
  --version 15 \
  --size db-s-1vcpu-1gb \
  --region nyc1

Self-Hosted PostgreSQL

If self-hosting, use Docker for consistency:

# docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: dubhe_indexer
      POSTGRES_USER: dubhe
      POSTGRES_PASSWORD: your_secure_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    restart: unless-stopped
 
volumes:
  postgres_data:

Database Configuration

Set your database connection string:

# For cloud databases
export DATABASE_URL="postgresql://dubhe:password@your-db-host:5432/dubhe_indexer"
 
# For self-hosted
export DATABASE_URL="postgresql://dubhe:password@localhost:5432/dubhe_indexer"

Step 3: Indexer Deployment

The indexer processes blockchain data and populates your database.

Docker Deployment

Create a production-ready Docker setup:

# Dockerfile.indexer
FROM ubuntu:22.04
 
# Install dependencies
RUN apt-get update && apt-get install -y \
    postgresql-client \
    ca-certificates \
    curl \
    && rm -rf /var/lib/apt/lists/*
 
# Download dubhe-indexer binary
RUN curl -L https://github.com/0xobelisk/dubhe/releases/latest/download/dubhe-indexer-linux-x64 \
    -o /usr/local/bin/dubhe-indexer && \
    chmod +x /usr/local/bin/dubhe-indexer
 
# Copy configuration
COPY dubhe.config.json /app/
WORKDIR /app
 
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:4000/health || exit 1
 
CMD ["dubhe-indexer", "--config", "dubhe.config.json", "--network", "testnet", "--with-graphql"]

Environment Variables

# .env.indexer
DATABASE_URL=postgresql://dubhe:password@db-host:5432/dubhe_indexer
SUI_RPC_URL=https://fullnode.testnet.sui.io:443  # or mainnet
RUST_LOG=info
WORKER_POOL_NUMBER=3
START_CHECKPOINT=0

Docker Compose for Indexer

# docker-compose.indexer.yml
version: '3.8'
services:
  indexer:
    build:
      context: .
      dockerfile: Dockerfile.indexer
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - SUI_RPC_URL=${SUI_RPC_URL}
      - RUST_LOG=info
    volumes:
      - ./dubhe.config.json:/app/dubhe.config.json:ro
    restart: unless-stopped
    depends_on:
      - postgres
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Cloud Deployment

AWS ECS:

{
  "family": "dubhe-indexer",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "indexer",
      "image": "your-registry/dubhe-indexer:latest",
      "environment": [
        {"name": "DATABASE_URL", "value": "postgresql://..."},
        {"name": "SUI_RPC_URL", "value": "https://fullnode.testnet.sui.io:443"}
      ]
    }
  ]
}

Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubhe-indexer
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dubhe-indexer
  template:
    metadata:
      labels:
        app: dubhe-indexer
    spec:
      containers:
      - name: indexer
        image: your-registry/dubhe-indexer:latest
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: dubhe-secrets
              key: database-url
        - name: SUI_RPC_URL
          value: "https://fullnode.testnet.sui.io:443"

Step 4: GraphQL Server Deployment

The GraphQL server provides API access to indexed data.

CLI-based Deployment

Start the GraphQL server using the built-in CLI:

# Basic startup
npx dubhe-graphql-server start
 
# With custom configuration
npx dubhe-graphql-server start \
  --port 4000 \
  --database-url "postgresql://dubhe:password@db-host:5432/dubhe_indexer" \
  --env production \
  --no-debug
 
# Production startup with all options
npx dubhe-graphql-server start \
  --port 4000 \
  --database-url "$DATABASE_URL" \
  --schema public \
  --endpoint /graphql \
  --env production \
  --cors \
  --subscriptions \
  --max-connections 100 \
  --query-timeout 30000

Using package.json scripts:

{
  "scripts": {
    "start": "dubhe-graphql-server start",
    "start:prod": "dubhe-graphql-server start --env production --no-debug"
  }
}

Environment Variables

# .env.graphql
DATABASE_URL=postgresql://dubhe:password@db-host:5432/dubhe_indexer
PORT=4000
NODE_ENV=production
PG_SCHEMA=public
GRAPHQL_ENDPOINT=/graphql
ENABLE_CORS=true
ENABLE_SUBSCRIPTIONS=true
DEBUG=false
QUERY_TIMEOUT=30000
MAX_CONNECTIONS=100
HEARTBEAT_INTERVAL=30000
ENABLE_METRICS=false
ENABLE_LIVE_QUERIES=true
ENABLE_PG_SUBSCRIPTIONS=true
ENABLE_NATIVE_WEBSOCKET=true

Docker Deployment

# Dockerfile.graphql
FROM node:18-alpine
 
WORKDIR /app
 
# Install GraphQL server package
RUN npm install -g @0xobelisk/graphql-server
 
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:4000/health || exit 1
 
EXPOSE 4000
 
# Start using CLI command
CMD ["npx", "dubhe-graphql-server", "start", "--env", "production"]

Docker Compose:

# docker-compose.graphql.yml
version: '3.8'
services:
  graphql-server:
    build:
      context: .
      dockerfile: Dockerfile.graphql
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - PORT=4000
      - NODE_ENV=production
      - ENABLE_CORS=true
      - ENABLE_SUBSCRIPTIONS=true
    ports:
      - "4000:4000"
    depends_on:
      - postgres
    restart: unless-stopped
    command: ["npx", "dubhe-graphql-server", "start", "--env", "production", "--cors", "--subscriptions"]

Cloud Deployment Options

Vercel (Not Recommended):

# Note: Vercel serverless functions don't support long-running processes
# For GraphQL server, use dedicated hosting like Railway, Render, or VPS
# If you must use Vercel, consider using their Edge Runtime with limitations:
 
# vercel.json
{
  "functions": {
    "api/graphql.js": {
      "runtime": "nodejs18.x"
    }
  }
}

Better alternatives for GraphQL server:

  • Railway (recommended)
  • Render
  • DigitalOcean App Platform
  • AWS ECS/Fargate
  • Google Cloud Run

Railway:

# railway.yml
build:
  builder: DOCKERFILE
  dockerfilePath: Dockerfile.graphql
deploy:
  startCommand: npx dubhe-graphql-server start --env production --cors --subscriptions
  healthcheckPath: /health
  healthcheckTimeout: 10

Railway with Node.js (without Docker):

{
  "name": "dubhe-graphql-server",
  "scripts": {
    "start": "npx dubhe-graphql-server start --env production"
  },
  "dependencies": {
    "@0xobelisk/graphql-server": "latest"
  }
}

Heroku:

{
  "name": "dubhe-graphql",
  "build": {
    "docker": {
      "web": "Dockerfile.graphql"
    }
  },
  "formation": {
    "web": {
      "quantity": 1,
      "size": "basic"
    }
  }
}

Heroku Procfile (without Docker):

web: npx dubhe-graphql-server start --port $PORT --env production --cors --subscriptions

Heroku package.json:

{
  "name": "dubhe-graphql-server",
  "scripts": {
    "start": "dubhe-graphql-server start --port $PORT --env production"
  },
  "dependencies": {
    "@0xobelisk/graphql-server": "latest"
  }
}

Step 5: Frontend Deployment

Deploy your frontend application independently from backend services.

Vercel Deployment

Automatic Deployment:

  1. Connect your GitHub repository to Vercel
  2. Configure environment variables in Vercel dashboard
  3. Deploy automatically on push to main branch

Manual Deployment:

# Install Vercel CLI
npm i -g vercel
 
# Deploy to production
vercel --prod
 
# Set environment variables
vercel env add NEXT_PUBLIC_NETWORK production
vercel env add NEXT_PUBLIC_PACKAGE_ID production
vercel env add NEXT_PUBLIC_GRAPHQL_URL production

Environment Variables for Frontend

# .env.production
NEXT_PUBLIC_NETWORK=testnet
NEXT_PUBLIC_PACKAGE_ID=0x...
NEXT_PUBLIC_DUBHE_SCHEMA_ID=0x...
NEXT_PUBLIC_GRAPHQL_URL=https://your-graphql-server.railway.app/graphql
NEXT_PUBLIC_GRAPHQL_WS_URL=wss://your-graphql-server.railway.app/graphql

Frontend Configuration

Update your frontend configuration to use production endpoints:

// src/config/production.ts
export const productionConfig = {
  network: process.env.NEXT_PUBLIC_NETWORK as NetworkType,
  packageId: process.env.NEXT_PUBLIC_PACKAGE_ID!,
  dubheSchemaId: process.env.NEXT_PUBLIC_DUBHE_SCHEMA_ID!,
  graphqlUrl: process.env.NEXT_PUBLIC_GRAPHQL_URL!,
  graphqlWsUrl: process.env.NEXT_PUBLIC_GRAPHQL_WS_URL!,
  rpcUrl: process.env.NEXT_PUBLIC_NETWORK === 'mainnet' 
    ? 'https://fullnode.mainnet.sui.io:443'
    : 'https://fullnode.testnet.sui.io:443'
};

Other Deployment Options

Netlify:

# netlify.toml
[build]
  command = "npm run build"
  publish = "out"
 
[build.environment]
  NEXT_PUBLIC_NETWORK = "testnet"
  NEXT_PUBLIC_PACKAGE_ID = "0x..."
 
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

AWS S3 + CloudFront:

# Build and deploy
npm run build
aws s3 sync out/ s3://your-bucket-name --delete
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"

Step 6: Configuration Management

Environment-Specific Configurations

Create separate configuration files for different environments:

// config/environments.ts
export const environments = {
  testnet: {
    network: 'testnet' as const,
    rpcUrl: 'https://fullnode.testnet.sui.io:443',
    faucetUrl: 'https://faucet.testnet.sui.io/gas',
    explorerUrl: 'https://testnet.suivision.xyz'
  },
  mainnet: {
    network: 'mainnet' as const,
    rpcUrl: 'https://fullnode.mainnet.sui.io:443',
    faucetUrl: null,
    explorerUrl: 'https://suivision.xyz'
  }
};

Secrets Management

AWS Secrets Manager:

# Store database URL
aws secretsmanager create-secret \
  --name "dubhe/production/database-url" \
  --secret-string "postgresql://..."
 
# Store private keys
aws secretsmanager create-secret \
  --name "dubhe/production/private-key" \
  --secret-string "your_private_key"

HashiCorp Vault:

# Store secrets in Vault
vault kv put secret/dubhe/production \
  database_url="postgresql://..." \
  private_key="your_private_key"

Configuration Validation

// config/validation.ts
import { z } from 'zod';
 
const configSchema = z.object({
  network: z.enum(['testnet', 'mainnet']),
  packageId: z.string().regex(/^0x[a-fA-F0-9]{64}$/),
  databaseUrl: z.string().url(),
  graphqlUrl: z.string().url(),
});
 
export function validateConfig(config: unknown) {
  return configSchema.parse(config);
}

Step 7: Monitoring and Maintenance

Health Checks

Implement health checks for all services:

// health-check.ts
export async function checkServices() {
  const services = [
    { name: 'Database', url: process.env.DATABASE_URL },
    { name: 'GraphQL', url: `${process.env.GRAPHQL_URL}/health` },
    { name: 'Indexer', url: `${process.env.INDEXER_URL}/health` },
  ];
 
  for (const service of services) {
    try {
      const response = await fetch(service.url);
      console.log(`✅ ${service.name}: ${response.status}`);
    } catch (error) {
      console.error(`❌ ${service.name}: ${error.message}`);
    }
  }
}

Logging Configuration

Structured Logging:

// logger.ts
import winston from 'winston';
 
export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

Monitoring Setup

Prometheus Metrics:

// metrics.ts
import prometheus from 'prom-client';
 
export const metrics = {
  httpRequests: new prometheus.Counter({
    name: 'http_requests_total',
    help: 'Total HTTP requests',
    labelNames: ['method', 'route', 'status']
  }),
  
  dbConnections: new prometheus.Gauge({
    name: 'database_connections_active',
    help: 'Active database connections'
  }),
  
  indexerBlocks: new prometheus.Counter({
    name: 'indexer_blocks_processed_total',
    help: 'Total blocks processed by indexer'
  })
};

Backup Strategy

Database Backups:

# Automated backup script
#!/bin/bash
BACKUP_DIR="/backups/$(date +%Y-%m-%d)"
mkdir -p $BACKUP_DIR
 
pg_dump $DATABASE_URL > $BACKUP_DIR/dubhe_backup.sql
gzip $BACKUP_DIR/dubhe_backup.sql
 
# Upload to S3
aws s3 cp $BACKUP_DIR/dubhe_backup.sql.gz s3://your-backup-bucket/

Step 8: Scaling Considerations

Database Scaling

Read Replicas:

// database.ts
export const dbConfig = {
  master: process.env.DATABASE_URL,
  replicas: [
    process.env.DATABASE_REPLICA_1_URL,
    process.env.DATABASE_REPLICA_2_URL,
  ].filter(Boolean)
};

Connection Pooling:

// pool.ts
import { Pool } from 'pg';
 
export const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

Indexer Scaling

For high-throughput networks, consider:

# Multiple indexer instances with different checkpoint ranges
dubhe-indexer --start-checkpoint 0 --end-checkpoint 1000000 --worker-pool-number 5
dubhe-indexer --start-checkpoint 1000001 --end-checkpoint 2000000 --worker-pool-number 5

GraphQL Server Scaling

Load Balancing:

# nginx.conf
upstream graphql_servers {
    server graphql1.example.com:4000;
    server graphql2.example.com:4000;
    server graphql3.example.com:4000;
}
 
server {
    listen 80;
    server_name api.example.com;
    
    location /graphql {
        proxy_pass http://graphql_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Troubleshooting

Common Issues

1. Database Connection Errors

# Check database connectivity
psql $DATABASE_URL -c "SELECT version();"
 
# Check connection limits
psql $DATABASE_URL -c "SELECT count(*) FROM pg_stat_activity;"

2. Indexer Sync Issues

# Check indexer logs
docker logs dubhe-indexer
 
# Restart from specific checkpoint
dubhe-indexer --start-checkpoint 1000000 --force

3. GraphQL Schema Issues

# Regenerate schema
curl -X POST http://localhost:4000/admin/schema/refresh
 
# Check table structure
psql $DATABASE_URL -c "\dt"

4. Frontend Connection Issues

// Debug network connectivity
console.log('GraphQL URL:', process.env.NEXT_PUBLIC_GRAPHQL_URL);
console.log('Network:', process.env.NEXT_PUBLIC_NETWORK);
 
// Test GraphQL endpoint
fetch(process.env.NEXT_PUBLIC_GRAPHQL_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: '{ __schema { types { name } } }' })
});

Performance Optimization

Database Optimization:

-- Create indexes for better query performance
CREATE INDEX idx_store_transactions_sender ON store_transactions(sender);
CREATE INDEX idx_store_events_timestamp ON store_events(timestamp);
 
-- Analyze query performance
EXPLAIN ANALYZE SELECT * FROM store_transactions WHERE sender = '0x...';

Caching Strategy:

// Redis caching for GraphQL
import Redis from 'ioredis';
 
const redis = new Redis(process.env.REDIS_URL);
 
export const cache = {
  async get(key: string) {
    const value = await redis.get(key);
    return value ? JSON.parse(value) : null;
  },
  
  async set(key: string, value: any, ttl = 300) {
    await redis.setex(key, ttl, JSON.stringify(value));
  }
};

Security Best Practices

Network Security

# Firewall rules (UFW example)
ufw allow 22/tcp    # SSH
ufw allow 80/tcp    # HTTP
ufw allow 443/tcp   # HTTPS
ufw allow 5432/tcp from 10.0.0.0/8  # PostgreSQL (internal only)
ufw deny 4000/tcp   # GraphQL (behind reverse proxy only)

Environment Variables

# Use strong, unique passwords
DATABASE_PASSWORD=$(openssl rand -base64 32)
JWT_SECRET=$(openssl rand -base64 64)
 
# Rotate keys regularly
PRIVATE_KEY_ROTATION_DATE=$(date +%Y-%m-%d)

SSL/TLS Configuration

# nginx SSL configuration
server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    
    location /graphql {
        proxy_pass http://localhost:4000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

This comprehensive guide covers all aspects of deploying Dubhe applications to production environments. Each service can be deployed independently, allowing for better scalability, maintainability, and security in production environments.