# ============================================================================= # Dotcom Arcade # ============================================================================= # The app serves the UI and API. With the "streaming" profile, a session # manager also streams games that can't run in a browser (Java, Shockwave, # Flash Player, Windows games), one container per player. # # Usage: # 1. Copy .env.example to .env and set the required variables # 2. docker compose up -d --build # (or, with IMAGE_PREFIX set to a registry's images: # docker compose pull && docker compose up -d) # 3. For streaming, set STREAMING_API_TOKEN and COMPOSE_PROFILES=streaming in # .env, then run `docker compose up -d --build` again # # Set PUID/PGID to your host user (`id -u`, `id -g`) so bind-mounted volumes # get the right ownership. # ============================================================================= services: app: build: . image: ${IMAGE_PREFIX:-}dotcom-arcade:${IMAGE_TAG:-latest} container_name: dotcom-arcade restart: unless-stopped ports: # Serves both the UI and the API (/api, /game-proxy, /game-zip) on one port - "${WEB_PORT:-80}:3100" volumes: # Read-write: the app records downloaded state in flashpoint.sqlite and # writes downloaded game data under Data/Games. - ${FLASHPOINT_HOST_PATH:?FLASHPOINT_HOST_PATH is required}:/data/flashpoint # Holds user.db and the installed Ruffle. Without this mount an in-app # Ruffle update is lost the next time the container is recreated. - ${DATA_PATH:-./data}:/app/data - ${LOGS_PATH:-./logs}:/app/logs environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - NODE_ENV=production - SERVE_FRONTEND=true - TZ=${TZ:-UTC} - DOMAIN=${DOMAIN:-http://localhost:${WEB_PORT:-80}} - LOG_LEVEL=${LOG_LEVEL:-info} # File I/O parallelism: images and ZIP reads run on libuv's threadpool, # whose default of 4 throttles every filesystem request in the process. - UV_THREADPOOL_SIZE=${UV_THREADPOOL_SIZE:-16} - JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required} - ENABLE_LOCAL_DB_COPY=${ENABLE_LOCAL_DB_COPY:-false} - SQLITE_CACHE_SIZE=${SQLITE_CACHE_SIZE:--64000} - SQLITE_MMAP_SIZE=${SQLITE_MMAP_SIZE:-268435456} - ENABLE_CACHE_PREWARM=${ENABLE_CACHE_PREWARM:-true} # Auth & rate limiting - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-1h} - BCRYPT_SALT_ROUNDS=${BCRYPT_SALT_ROUNDS:-10} - RATE_LIMIT_WINDOW_MS=${RATE_LIMIT_WINDOW_MS:-60000} - RATE_LIMIT_MAX_REQUESTS=${RATE_LIMIT_MAX_REQUESTS:-100} # Application # "Latest release" API URL (GitHub or Gitea) for update checks; off when unset - APP_UPDATE_URL=${APP_UPDATE_URL:-} - ENABLE_CGI=${ENABLE_CGI:-false} # Streamed play for games that can't run in a browser (off unless the token is set) - STREAMING_API_URL=${STREAMING_API_URL:-http://session-manager:8080} - STREAMING_STREAM_URL=${STREAMING_STREAM_URL:-http://session-manager:8081} - STREAMING_API_TOKEN=${STREAMING_API_TOKEN:-} # Sign in users named by an authenticating reverse proxy (off unless both are set) - TRUSTED_HEADER_USERNAME=${TRUSTED_HEADER_USERNAME:-} - TRUSTED_HEADER_EMAIL=${TRUSTED_HEADER_EMAIL:-} - TRUSTED_PROXIES=${TRUSTED_PROXIES:-} # OpenTelemetry - OTEL_ENABLED=${OTEL_ENABLED:-false} - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-} - OTEL_API_KEY=${OTEL_API_KEY:-} - OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-dotcom-arcade} - OTEL_TRACES_ENABLED=${OTEL_TRACES_ENABLED:-true} - OTEL_METRICS_ENABLED=${OTEL_METRICS_ENABLED:-true} - OTEL_METRICS_EXPORT_INTERVAL=${OTEL_METRICS_EXPORT_INTERVAL:-60000} - OTEL_LOG_LEVEL=${OTEL_LOG_LEVEL:-info} healthcheck: test: ["CMD", "curl", "--fail", "http://localhost:3100/health"] interval: 30s timeout: 10s retries: 3 # The port opens early and /health reports "starting" until the database # copy and cache pre-warm finish, which is minutes on a network share. start_period: 300s logging: driver: json-file options: max-size: "10m" max-file: "3" deploy: resources: limits: cpus: "2.0" memory: 2G reservations: cpus: "0.5" memory: 512M networks: - arcade # Starts a streamer container per player. The app proxies /stream/ to it, so # it publishes no ports. session-manager: profiles: [streaming] build: streaming/session-manager image: ${IMAGE_PREFIX:-}dotcom-arcade-session-manager:${IMAGE_TAG:-latest} container_name: dotcom-arcade-session-manager restart: unless-stopped volumes: # Starts and removes streamer containers. Docker socket access is # effectively root on the host. - /var/run/docker.sock:/var/run/docker.sock - ${FLASHPOINT_HOST_PATH:?FLASHPOINT_HOST_PATH is required}:/flashpoint:ro environment: - FLASHPOINT_HOST_PATH # The session manager refuses to start without it - API_TOKEN=${STREAMING_API_TOKEN:-} - STREAMER_IMAGE=${IMAGE_PREFIX:-}dotcom-arcade-streamer:${IMAGE_TAG:-latest} - STREAMER_NETWORK=dotcom-arcade-streams - MAX_SESSIONS=${MAX_SESSIONS:-4} - STREAM_PLATFORMS=${STREAM_PLATFORMS:-Java,Shockwave,Flash} - PUID=${PUID:-1000} - PGID=${PGID:-1000} networks: - arcade - streams # The image the session manager runs per player; built here, never started by compose streamer: profiles: [streaming] build: streaming/streamer image: ${IMAGE_PREFIX:-}dotcom-arcade-streamer:${IMAGE_TAG:-latest} scale: 0 networks: arcade: name: dotcom-arcade # Streamer containers: reachable only from the session manager, no internet access streams: name: dotcom-arcade-streams internal: true