Skip to main content

Docker Deployment Guide

This guide covers Docker deployment for Hyperscape, including recent fixes and best practices for production deployments.

Overview

Hyperscape uses a multi-stage Docker build that produces a single image containing both the game server and web client. The build process handles several platform-specific challenges related to Bun workspace management, native dependencies, and Vite compilation.

Dockerfile Architecture

Build Stages

  1. node-build-tools: Provides real Node.js binary for Vite builds
  2. builder: Bun-based build stage that compiles all packages
  3. runtime: Node.js 22 runtime for production server

Runtime Requirements

Critical: The production server MUST run under Node.js 22+, not Bun. Reason: uWebSockets.js native bindings depend on Node’s N-API and fail under Bun’s node compatibility shim. The March 2026 performance overhaul requires uWS for 50+ concurrent players with 25+ AI agents. Runtime Image: node:22-trixie-slim
  • GLIBC Requirement: uWebSockets.js requires GLIBC ≥ 2.38
  • Debian Trixie: Provides GLIBC 2.38+ (Bookworm only has 2.36)
  • Slim Variant: Minimal image size while including required system libraries

Building the Image

Basic Build

Multi-Platform Build

Build Arguments

Known Issues and Workarounds

Issue 1: better-sqlite3 QEMU Segfault

Problem: better-sqlite3 node-gyp build segfaults under QEMU cross-compilation (e.g., building linux/amd64 on macOS ARM). Workaround: Strip better-sqlite3 from package manifests before install:
Safe: Hyperscape uses bun:sqlite (dev) and PostgreSQL (production), so better-sqlite3 is not needed. Problem: Docker COPY flattens symlinks. Bun workspaces use symlinks in node_modules/@hyperscape/* to reference local packages. Workaround: Manually recreate workspace symlinks in runtime stage:

Issue 3: Bun Per-Package node_modules

Problem: Bun 1.3+ uses per-package node_modules (not flat hoisting). When Bun hoists deps without materializing directories, Docker COPY --from=builder fails with “file not found”. Workaround: Defensively create all required directories before COPY:
When Added: April 6, 2026 (Commits fca9ffb-cb237b6)

Issue 4: Vite 8 Requires Node 22.12+

Problem: Bun 1.1.38 reports Node 22.6.0 when running Vite, but Vite 8 requires Node 22.12+. Workaround: Copy real Node.js binary from node:22-bookworm-slim for Vite build steps:
When Added: April 6, 2026 (Commit 58a18df)

Environment Variables

Build-Time Variables

Runtime Variables

Required:
Optional:
See packages/server/.env.example for complete list.

Running the Container

Basic Run

With Environment File

With Volume Mounts

Health Checks

The Dockerfile includes a health check that verifies the server is responding:
Check Status:
View Health Logs:

Production Deployment

Railway

Railway deployment uses the Dockerfile automatically:
Configuration: See docs/railway-dev-prod.md for Railway-specific setup.

Manual Deployment

  1. Build Image:
  2. Push to Registry:
  3. Deploy:

Troubleshooting

Build Failures

Missing node_modules directories: Error:
Cause: Bun hoisted dependencies without materializing per-package node_modules. Fix: Update to latest Dockerfile (April 2026) which includes defensive mkdir -p commands. Verification:
Vite build failures: Error:
Cause: Bun’s Node compatibility shim doesn’t fully support Vite 8. Fix: Use real Node.js for Vite builds (already in latest Dockerfile):
better-sqlite3 segfault: Error:
Cause: node-gyp cross-compilation under QEMU. Fix: Strip better-sqlite3 from manifests before install (already in latest Dockerfile).

Runtime Failures

uWebSockets.js binding errors: Error:
Cause: Running under Bun instead of Node.js, or wrong GLIBC version. Fix:
  1. Verify runtime image is node:22-trixie-slim (not oven/bun:*)
  2. Verify GLIBC ≥ 2.38: ldd --version
  3. Check CMD uses node, not bun
Missing workspace symlinks: Error:
Cause: Workspace symlinks not recreated in runtime stage. Fix: Verify Dockerfile includes symlink creation:

Performance Optimization

Build Cache

Use BuildKit cache mounts to speed up rebuilds:

Multi-Stage Optimization

The Dockerfile uses multi-stage builds to minimize final image size:
  • Builder stage: Includes build tools (Python, make, g++, pkg-config)
  • Runtime stage: Only includes runtime dependencies (libcairo, libpango, etc.)
Image Size Comparison:
  • Builder stage: ~2.5GB (includes build tools)
  • Runtime stage: ~450MB (production-ready)

Security Considerations

Secrets Management

Never bake secrets into the image:

Non-Root User

The current Dockerfile runs as root. For production, consider adding a non-root user:

Minimal Attack Surface

The runtime image uses node:22-trixie-slim which:
  • Excludes unnecessary packages
  • Reduces attack surface
  • Minimizes image size

See Also

  • Dockerfile.server - Production Dockerfile
  • docs/railway-dev-prod.md - Railway deployment guide
  • packages/server/.env.example - Environment variable reference
  • .dockerignore - Files excluded from build context