> ## Documentation Index
> Fetch the complete documentation index at: https://hyperscape-ai-mintlify-docs-update-1773296097571.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={null}
    # 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={null}
    # 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={null}
    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={null}
    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={null}
# .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={null}
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={null}
[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={null}
// 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={null}
    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={null}
    # 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={null}
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:

```typescript theme={null}
// 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={null}
    npm install -g wrangler
    ```
  </Step>

  <Step title="Authenticate">
    ```bash theme={null}
    wrangler login
    ```
  </Step>

  <Step title="Upload manifests">
    ```bash theme={null}
    # 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={null}
    # 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={null}
// 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={null}
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={null}
# 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
