Skip to main content

Deployment Overview

Hyperscape uses a split deployment architecture with separate environments for production and staging:

Production Architecture

Staging Architecture

Deployment Triggers

BranchEnvironmentFrontendAPIAssets
mainProductionAuto-deploy to PagesAuto-deploy to RailwayAuto-upload to R2
stagingStagingAuto-deploy to PagesAuto-deploy to RailwayAuto-upload to R2 staging
The staging environment provides a complete isolated instance for testing changes before production deployment.

Environment Variables

Server Environment Variables

# Required
DATABASE_URL=postgresql://...
JWT_SECRET=your-secret-key
PUBLIC_PRIVY_APP_ID=your-app-id
PRIVY_APP_SECRET=your-app-secret

# CDN Configuration
PUBLIC_CDN_URL=https://assets.hyperscape.club
PUBLIC_API_URL=https://api.hyperscape.club
PUBLIC_WS_URL=wss://api.hyperscape.club/ws

# Optional
PORT=5555
NODE_ENV=production
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...

Client Environment Variables

PUBLIC_PRIVY_APP_ID=your-app-id
PUBLIC_API_URL=https://api.hyperscape.club
PUBLIC_WS_URL=wss://api.hyperscape.club/ws
PUBLIC_CDN_URL=https://assets.hyperscape.club
PUBLIC_APP_URL=https://hyperscape.club
PUBLIC_PRIVY_APP_ID must match between client and server for authentication to work.

Railway Deployment (Backend/API)

Railway hosts the game server with automatic deployments via GitHub Actions.

Automated Deployment

The repository includes a GitHub Actions workflow that automatically deploys to Railway on every push to main:
# .github/workflows/deploy-railway.yml
name: Deploy to Railway
on:
  push:
    branches: [main]
    paths:
      - 'packages/shared/**'
      - 'packages/server/**'
      - 'packages/client/**'
      - 'packages/plugin-hyperscape/**'
Deployment Process:
  1. Push to main branch triggers workflow
  2. GitHub Actions calls Railway GraphQL API
  3. Railway rebuilds using Nixpacks configuration
  4. Server restarts with new code
  5. Manifests fetched from CDN at startup

Manual Setup

Hyperscape includes automated Railway deployment via GitHub Actions. The server is deployed using Nixpacks with automatic manifest fetching from CDN.

Architecture

The production deployment uses a split architecture:
  • Frontend: Cloudflare Pages (hyperscape.club)
  • Server/API: Railway (hyperscape-production.up.railway.app)
  • Assets/CDN: Cloudflare R2 (assets.hyperscape.club)
  • Database: Railway PostgreSQL

Automated Deployment

Deployments trigger automatically on push to main when these paths change:
paths:
  - 'packages/shared/**'
  - 'packages/client/**'
  - 'packages/server/**'
  - 'packages/plugin-hyperscape/**'
  - 'package.json'
  - 'bun.lock'
  - 'nixpacks.toml'
  - 'railway.server.json'
  - 'Dockerfile.server'
The GitHub Actions workflow (.github/workflows/deploy-railway.yml) uses Railway’s GraphQL API to trigger redeployments.

Manual Deployment

Railway deployment is automated via GitHub Actions (.github/workflows/deploy-railway.yml). Railway deployment is automated via GitHub Actions for both production and staging environments.

Automated Deployment

The .github/workflows/deploy-railway.yml workflow automatically deploys when you push to:
  • main branch → Production environment
  • staging branch → Staging environment
Trigger paths (deployment only runs when these files change):
  • packages/shared/**
  • packages/client/**
  • packages/server/**
  • packages/plugin-hyperscape/**
  • package.json, bun.lock
  • nixpacks.toml, railway.server.json, Dockerfile.server

Railway Configuration

The deployment uses nixpacks for building (configured in nixpacks.toml):
[phases.setup]
aptPkgs = ["python3", "make", "g++", "pkg-config", "libcairo2-dev", ...]

[phases.install]
cmds = ["bun install"]

[phases.build]
cmds = [
  "bun run build:shared",
  "bun run build:server",
  "mkdir -p packages/server/world/assets/manifests"
]

[start]
cmd = "cd packages/server && bun dist/index.js"
The build phase creates the manifests directory but does NOT download manifests. Manifests are fetched from the CDN at server startup.

Manual Railway Setup

1

Create Railway project

  1. Connect your GitHub repository to Railway
  2. Create two environments: production and staging
2

Configure service

Railway automatically detects nixpacks.toml and railway.server.json.Build settings:
  • Builder: Nixpacks
  • Config path: nixpacks.toml
  • Watch patterns: Defined in railway.server.json
Deploy settings:
  • Start command: cd packages/server && bun dist/index.js
  • Healthcheck path: /status
  • Healthcheck timeout: 300s
3

Add PostgreSQL

Add PostgreSQL service from Railway marketplace for each environment.
4

Set environment variables

Configure environment variables for each environment (see tabs above).Required secrets:
  • DATABASE_URL (auto-populated by Railway PostgreSQL)
  • JWT_SECRET
  • PUBLIC_PRIVY_APP_ID
  • PRIVY_APP_SECRET
  • PUBLIC_CDN_URL
5

Configure GitHub Actions

Add these secrets to your GitHub repository:
  • RAILWAY_TOKEN - Railway API token
Add these variables:
  • RAILWAY_STAGING_SERVICE_ID - Staging service ID
  • RAILWAY_STAGING_ENVIRONMENT_ID - Staging environment ID

Manifest Fetching at Startup

The server automatically fetches manifests from the CDN at startup:
// From packages/server/src/startup/config.ts
await fetchManifestsFromCDN(CDN_URL, manifestsDir, NODE_ENV);
Behavior:
  • Production/Staging: Always fetches latest manifests from CDN
  • Development: Skips fetch if local manifests already exist
  • Caching: Compares content and only writes changed files
This ensures the server always has the latest game data without bundling large manifest files in the Docker image.

Cloudflare Pages Deployment

Cloudflare Pages automatically deploys the frontend via GitHub integration.

Automatic Deployment

Production (main branch):
  • URL: hyperscape.club
  • Custom domain configured in Cloudflare Pages settings
Staging (staging branch):

Manual Setup

1

Connect repository

  1. Go to Cloudflare Pages dashboard
  2. Create new project from GitHub
  3. Select the Hyperscape repository
2

Configure build

Framework preset: None (custom)Build settings:
  • Build command: cd packages/client && bun install && bun run build
  • Build output directory: packages/client/dist
  • Root directory: / (repository root)
Environment variables (set in Pages dashboard):
  • PUBLIC_PRIVY_APP_ID
  • PUBLIC_API_URL
  • PUBLIC_WS_URL
  • PUBLIC_CDN_URL
  • PUBLIC_APP_URL
3

Configure custom domains

Production branch (main):
  • Primary: hyperscape.club
  • Alternate: www.hyperscape.club
Staging branch (staging):
  • Primary: staging.hyperscape.club
4

Configure CORS

The server automatically allows these origins:
  • https://hyperscape.club
  • https://www.hyperscape.club
  • https://hyperscape.pages.dev
  • http://hyperscape.pages.dev
  • https://*.hyperscape.pages.dev (preview deployments)
Ensure PUBLIC_PRIVY_APP_ID matches between Cloudflare Pages and Railway environments.

Database Setup

Railway PostgreSQL is automatically configured when you add the database service.
1

Add database service

In your Railway project:
  1. Click “New” → “Database” → “PostgreSQL”
  2. Railway automatically sets DATABASE_URL environment variable
  3. Database is ready immediately
2

Run migrations

Migrations run automatically on server startup. To run manually:
cd packages/server
bunx drizzle-kit push      # Apply schema changes
bunx drizzle-kit migrate   # Run pending migrations

Alternative: Neon PostgreSQL

For serverless PostgreSQL with better free tier:
  1. Create database at neon.tech
  2. Copy connection string
  3. Set as DATABASE_URL in Railway environment variables

CDN & Assets Setup

Cloudflare R2 (Production)

Hyperscape uses Cloudflare R2 for asset storage with global CDN distribution.
1

Create R2 bucket

  1. Go to Cloudflare dashboard → R2
  2. Create bucket named hyperscape-assets
  3. Enable public access
2

Upload assets

Use the sync script to upload assets:
# Set Cloudflare credentials
export CLOUDFLARE_ACCOUNT_ID=your-account-id
export CLOUDFLARE_API_TOKEN=your-api-token

# Upload assets to R2
bun run sync:r2
3

Configure custom domain

  1. In R2 bucket settings, add custom domain: assets.hyperscape.club
  2. Cloudflare auto-configures DNS and SSL
  3. Set PUBLIC_CDN_URL=https://assets.hyperscape.club in both server and client

Local CDN (Development)

For local development, use the Docker CDN:
bun run cdn:up    # Start nginx CDN container
bun run cdn:down  # Stop CDN container

Cloudflare R2 Asset Deployment

CDN Architecture

Hyperscape uses a CDN-based architecture for serving game assets (models, textures, audio, manifests): Required Manifests:
  • npcs.json - NPC and mob definitions
  • world-areas.json - Zone configuration with station spawns
  • biomes.json - Biome definitions
  • stores.json - Shop inventories
  • items.json OR items/{weapons,tools,resources,food,misc}.json - Item definitions
Asset Structure:
assets/
├── manifests/          # JSON game data
├── world/              # Environment models and textures
├── models/             # 3D models (GLB)
├── audio/              # Sound effects and music
└── web/                # PhysX WASM runtime

Development CDN Fallback

In development, if local CDN is incomplete, the server automatically falls back to production CDN:
// From packages/server/src/startup/config.ts
// If localhost CDN is missing required manifests, fall back to production
if (isLocalhostUrl(cdnUrl) && missingRequired.length > 0) {
  const fallbackCdnUrl = "https://assets.hyperscape.club";
  await fetchFrom(fallbackCdnUrl);
}
This allows local development without cloning the full assets repository.

Option 1: Self-Hosted

Automatic Upload

The .github/workflows/deploy-cloudflare.yml workflow uploads assets when:
  • Manifests change: packages/server/world/assets/manifests/**
  • Assets change: assets/**
  • Manual trigger via workflow dispatch
Buckets:
  • Production: hyperscape-assetsassets.hyperscape.club
  • Staging: hyperscape-assets-stagingstaging-assets.hyperscape.club

Manual R2 Upload

1

Install Wrangler

npm install -g wrangler
2

Authenticate

wrangler login
3

Upload manifests

# Production
find packages/server/world/assets/manifests -type f -name "*.json" | while read file; do
  rel_path="${file#packages/server/world/assets/manifests/}"
  wrangler r2 object put "hyperscape-assets/manifests/$rel_path" \
    --file="$file" \
    --content-type="application/json"
done

# Staging
# Replace bucket name with hyperscape-assets-staging
4

Upload 3D assets

# Upload models, textures, audio from assets/ directory
find assets -type f | while read file; do
  rel_path="${file#assets/}"
  wrangler r2 object put "hyperscape-assets/$rel_path" \
    --file="$file" \
    --content-type="application/octet-stream"
done

CDN Configuration

The server fetches manifests from the CDN at startup:
// From packages/server/src/startup/config.ts
await fetchManifestsFromCDN(CDN_URL, manifestsDir, NODE_ENV);
Manifest files fetched:
  • Root manifests: npcs.json, stores.json, world-areas.json, etc.
  • Items: items/weapons.json, items/tools.json, items/resources.json, etc.
  • Gathering: gathering/woodcutting.json, gathering/mining.json, etc.
  • Recipes: recipes/cooking.json, recipes/smithing.json, etc.
Caching behavior:
  • Manifests cached locally in packages/server/world/assets/manifests/
  • Only updated files are written (content comparison)
  • Development mode skips fetch if local manifests exist
  • 5-minute cache headers for manifest HTTP requests
This architecture allows updating game content by uploading new manifests to R2 without redeploying the server.

Local CDN (Development)

For local development, use the Docker CDN:
bun run cdn:up          # Start CDN container
bun run cdn:down        # Stop CDN container
bun run cdn:restart     # Force restart
The CDN serves from packages/server/world/assets by default. Configure PUBLIC_CDN_URL to point to your CDN host.

Option 2: Cloud Storage (Cloudflare R2)

Upload assets to Cloudflare R2 or similar:
  1. Ensure assets are present: bun run ensure-assets
  2. Upload packages/server/world/assets/ to R2 bucket
  3. Set PUBLIC_CDN_URL to bucket URL (e.g., https://assets.hyperscape.club)
Cloudflare R2 Deployment:
  • Automated via .github/workflows/deploy-r2.yml
  • Triggers on push to main branch
  • Uses wrangler to sync assets to R2

Asset Management

# Download full assets (models, audio, textures)
bun run ensure-assets

# Sync assets from git repository
bun run assets:sync

# Verify CDN is serving correctly
bun run cdn:verify
The ensure-assets script detects if you have manifests-only vs full assets and automatically downloads missing content via Git LFS.

Production Checklist

  • PostgreSQL database provisioned
  • Environment variables configured
  • Privy credentials set (both client and server)
  • CDN serving assets
  • WebSocket URL configured
  • SSL/TLS enabled
  • CORS allowlist includes production domains