> ## Documentation Index
> Fetch the complete documentation index at: https://hyperscape-ai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Deploy Hyperscape to production environments

## Deployment Overview

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

### Production Architecture

* **Frontend**: Cloudflare Pages ([hyperscape.club](https://hyperscape.club))
* **API/Server**: Railway ([api.hyperscape.club](https://api.hyperscape.club))
* **Database**: PostgreSQL on Railway
* **Assets/CDN**: Cloudflare R2 ([assets.hyperscape.club](https://assets.hyperscape.club))

### Staging Architecture

* **Frontend**: Cloudflare Pages ([staging.hyperscape.club](https://staging.hyperscape.club))
* **API/Server**: Railway ([staging-api.hyperscape.club](https://staging-api.hyperscape.club))
* **Database**: PostgreSQL on Railway (separate staging database)
* **Assets/CDN**: Cloudflare R2 staging bucket ([staging-assets.hyperscape.club](https://staging-assets.hyperscape.club))

### Deployment Triggers

| Branch    | Environment | Frontend             | API                    | Assets                    |
| --------- | ----------- | -------------------- | ---------------------- | ------------------------- |
| `main`    | Production  | Auto-deploy to Pages | Auto-deploy to Railway | Auto-upload to R2         |
| `staging` | Staging     | Auto-deploy to Pages | Auto-deploy to Railway | Auto-upload to R2 staging |

<Info>
  The staging environment provides a complete isolated instance for testing changes before production deployment.
</Info>

## Environment Variables

### Server Environment Variables

<Tabs>
  <Tab title="Production">
    ```bash theme={"theme":{"light":"github-light","dark":"css-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=...
    ```
  </Tab>

  <Tab title="Staging">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    # Required
    DATABASE_URL=postgresql://...  # Separate staging database
    JWT_SECRET=your-secret-key
    PUBLIC_PRIVY_APP_ID=your-app-id
    PRIVY_APP_SECRET=your-app-secret

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

    # Optional
    PORT=5555
    NODE_ENV=production
    ```
  </Tab>
</Tabs>

### Client Environment Variables

<Tabs>
  <Tab title="Production">
    ```bash theme={"theme":{"light":"github-light","dark":"css-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
    ```
  </Tab>

  <Tab title="Staging">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    PUBLIC_PRIVY_APP_ID=your-app-id
    PUBLIC_API_URL=https://staging-api.hyperscape.club
    PUBLIC_WS_URL=wss://staging-api.hyperscape.club/ws
    PUBLIC_CDN_URL=https://staging-assets.hyperscape.club
    PUBLIC_APP_URL=https://staging.hyperscape.club
    ```
  </Tab>
</Tabs>

<Warning>
  `PUBLIC_PRIVY_APP_ID` must match between client and server for authentication to work.
</Warning>

## 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`:

```yaml theme={"theme":{"light":"github-light","dark":"css-variables"}}
# .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:

```yaml theme={"theme":{"light":"github-light","dark":"css-variables"}}
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`):

```toml theme={"theme":{"light":"github-light","dark":"css-variables"}}
[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"
```

<Info>
  The build phase creates the manifests directory but does NOT download manifests. Manifests are fetched from the CDN at server startup.
</Info>

### Manual Railway Setup

<Steps>
  <Step title="Create Railway project">
    1. Connect your GitHub repository to Railway
    2. Create two environments: `production` and `staging`
  </Step>

  <Step title="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
  </Step>

  <Step title="Add PostgreSQL">
    Add PostgreSQL service from Railway marketplace for each environment.
  </Step>

  <Step title="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`
  </Step>

  <Step title="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
  </Step>
</Steps>

### Manifest Fetching at Startup

The server automatically fetches manifests from the CDN at startup:

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// 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](https://hyperscape.club)
* Custom domain configured in Cloudflare Pages settings

**Staging** (`staging` branch):

* URL: [staging.hyperscape.club](https://staging.hyperscape.club)
* Preview deployments: `*.hyperscape.pages.dev`

### Manual Setup

<Steps>
  <Step title="Connect repository">
    1. Go to Cloudflare Pages dashboard
    2. Create new project from GitHub
    3. Select the Hyperscape repository
  </Step>

  <Step title="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`
  </Step>

  <Step title="Configure custom domains">
    **Production branch** (`main`):

    * Primary: `hyperscape.club`
    * Alternate: `www.hyperscape.club`

    **Staging branch** (`staging`):

    * Primary: `staging.hyperscape.club`
  </Step>

  <Step title="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)
  </Step>
</Steps>

<Warning>
  Ensure `PUBLIC_PRIVY_APP_ID` matches between Cloudflare Pages and Railway environments.
</Warning>

## Database Setup

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

### Railway PostgreSQL (Recommended)

<Steps>
  <Step title="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
  </Step>

  <Step title="Run migrations">
    Migrations run automatically on server startup. To run manually:

    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    cd packages/server
    bunx drizzle-kit push      # Apply schema changes
    bunx drizzle-kit migrate   # Run pending migrations
    ```
  </Step>
</Steps>

### Alternative: Neon PostgreSQL

For serverless PostgreSQL with better free tier:

1. Create database at [neon.tech](https://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.

<Steps>
  <Step title="Create R2 bucket">
    1. Go to Cloudflare dashboard → R2
    2. Create bucket named `hyperscape-assets`
    3. Enable public access
  </Step>

  <Step title="Upload assets">
    Use the sync script to upload assets:

    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    # 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
    ```
  </Step>

  <Step title="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
  </Step>
</Steps>

### Local CDN (Development)

For local development, use the Docker CDN:

```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
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
```

### CORS Configuration for Assets

The assets CDN is configured with CORS headers to allow cross-origin loading from game clients:

```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Max-Age: 86400
```

This enables the game client to load 3D models, textures, and manifests from the CDN regardless of the client's origin. The CORS configuration is automatically applied to all assets served from Cloudflare R2.

### Development CDN Fallback

In development, if local CDN is incomplete, the server automatically falls back to production CDN:

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// 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-assets` → `assets.hyperscape.club`
* Staging: `hyperscape-assets-staging` → `staging-assets.hyperscape.club`

### Manual R2 Upload

<Steps>
  <Step title="Install Wrangler">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    npm install -g wrangler
    ```
  </Step>

  <Step title="Authenticate">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    wrangler login
    ```
  </Step>

  <Step title="Upload manifests">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    # 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
    ```
  </Step>

  <Step title="Upload 3D assets">
    ```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
    # 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
    ```
  </Step>
</Steps>

### CDN Configuration

The server fetches manifests from the CDN at startup:

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// 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

<Info>
  This architecture allows updating game content by uploading new manifests to R2 without redeploying the server.
</Info>

### Local CDN (Development)

For local development, use the Docker CDN:

```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
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

```bash theme={"theme":{"light":"github-light","dark":"css-variables"}}
# 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
```

<Info>
  The `ensure-assets` script detects if you have manifests-only vs full assets and automatically downloads missing content via Git LFS.
</Info>

## 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
