Files
dotcom-arcade/docs/09-deployment/upgrade-guide.md
T
Darkraise 1973adabca docs: add upgrade guide for the single-image deployment
Deployments on 1.0.31 or earlier cannot take an image update without
changing their compose file and .env, and nothing in the repo said so.

Document the migration from the two-container setup to the single
flashpoint-web image, the environment variables added, removed, and
changed since 1.0.31, and per-version release notes for 1.0.32-1.0.36.
Link it from the README and both documentation indexes.
2026-08-12 10:02:15 +07:00

12 KiB
Raw Blame History

Upgrade Guide and Release Notes

How to move an existing deployment to a newer version, and what changed in each release. Versions follow the git tags published on Docker Hub.

Upgrading from 1.0.31 or earlier? The deployment model changed in 1.0.32: the separate backend and frontend containers were replaced by a single image. Read Upgrading from 1.0.31 before pulling.


Upgrading from 1.0.31

What changed

Up to 1.0.31 a deployment ran two containers: darkraise/flashpoint-backend (Node/Express, port 3100) and darkraise/flashpoint-frontend (nginx serving the built React app on port 8080, proxying /api to the backend).

From 1.0.32 there is one image, darkraise/flashpoint-web. The Express backend serves the built React UI and its SPA fallback alongside the API and game content, so the whole app lives on one origin and one port.

Before (<= 1.0.31)                     After (>= 1.0.32)
  flashpoint-frontend  :80  -> nginx     flashpoint-web  :80 -> :3100
  flashpoint-backend   :3100 -> Express    /            built React UI
                                           /api, /game-proxy, /game-zip

The old flashpoint-backend and flashpoint-frontend images are no longer built or published. 1.0.31 is the last version available under those names.

Migration steps

  1. Stop and remove the old stack. From the directory holding your existing compose file:

    docker compose down
    

    This removes the flashpoint-backend and flashpoint-frontend containers. Named volumes and bind mounts are kept.

  2. Replace docker-compose.yml with the single-service version from the README. The service is now named flashpoint-web, maps ${WEB_PORT:-80} to container port 3100, and has no frontend service and no depends_on.

  3. Update .env — see the variable tables below. At minimum, delete API_PORT, BACKEND_HOST, and BACKEND_PORT; they are no longer read by anything.

  4. Check the Flashpoint mount. As of 1.0.36 the shipped compose mounts it read-write (no :ro). Downloading games, recording downloaded state in flashpoint.sqlite, and metadata sync all write into that directory. Leaving :ro in place still allows browsing and playing already-downloaded games, but the Downloaded page will stay empty and the reconciler logs a warning on every boot.

  5. Update your reverse proxy, if you have one. Configurations that defined two upstreams — the UI on the web port and the API on port 3100 — must be collapsed into a single upstream on WEB_PORT. Any rule routing /api to port 3100 will break, because nothing listens there on the host anymore. Firewall rules opening port 3100 can be removed.

  6. Pull and start.

    docker compose pull && docker compose up -d
    

What is preserved

Application data is untouched by the migration. user.db (accounts, playlists, favourites, play history, settings) lives in the DATA_PATH bind mount at /app/data and is used unchanged by the new image. Log and Flashpoint mounts keep their paths. No database migration is required beyond the ones the backend applies on startup.

Environment variable changes

Removed — delete these from .env; they have no effect:

Variable Replacement
API_PORT WEB_PORT — one port now serves both the UI and API
BACKEND_HOST none — there is no cross-container proxy
BACKEND_PORT none — same reason

PORT and HOST were documented as configurable before 1.0.32 but never were: the backend binds 0.0.0.0:3100 unconditionally, and the Docker EXPOSE and healthcheck depend on that. The docs were corrected rather than the code.

Added:

Variable Default Since Description
SERVE_FRONTEND true in production, else false 1.0.32 Serve the built UI and SPA fallback from the backend
FRONTEND_DIST_PATH /app/frontend/dist in image 1.0.32 Where the built UI lives; rarely needs overriding
FLASHPOINT_GAMES_PATH <FLASHPOINT_PATH>/Data/Games 1.0.32 Override the game ZIP directory
OTEL_METRICS_EXPORT_INTERVAL 60000 1.0.32 Metrics export interval in milliseconds
OTEL_LOG_LEVEL info 1.0.32 OpenTelemetry SDK log level
COOKIE_SECURE auto 1.0.34 Secure flag on auth cookies: auto, true, false
UV_THREADPOOL_SIZE 16 1.0.36 File I/O threads; Node's own default of 4 throttles image reads
DB_WORKER_COUNT 4 1.0.36 Read-only SQLite query worker threads (1–16)

UV_THREADPOOL_SIZE=16 is baked into the image, so it applies whether or not your compose file passes it.

Changed:

Variable Change
DOMAIN Now accepts a comma-separated list of origins. Same-origin requests are always allowed, so a standard single-image install needs no value at all. Default ports are normalized: http://host:80 and http://host are treated as the same origin.
IMAGE_TAG Selects the tag of darkraise/flashpoint-web instead of the two old images.
OTEL_SERVICE_NAME Default changed from flashpoint-web-backend to flashpoint-web. Update dashboards and alert queries that filter on the old name.
LOGS_PATH Dev compose mounts it at /app/logs directly; it previously mounted ${LOGS_PATH}/backend.

Newly passed through compose (these already existed in the backend, but the shipped compose files now forward them with documented defaults): JWT_EXPIRES_IN, BCRYPT_SALT_ROUNDS, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_REQUESTS, HOME_RECENT_HOURS, ENABLE_CGI.

FLASHPOINT_PATH=/data/flashpoint is now set explicitly in the dev compose file. The backend's fallback only points there when NODE_ENV=production, so a development container previously looked at the wrong path.

Rolling back

Pin the old images (darkraise/flashpoint-backend:1.0.31 and darkraise/flashpoint-frontend:1.0.31) and restore the two-service compose file and the .env entries removed in step 3. Application data written by 1.0.32+ is readable by 1.0.31. Downloaded state is recorded in Flashpoint's own presentOnDisk and path columns, which the Launcher already understands; 1.0.31 simply does not surface it.


[1.0.36] — 2026-08-12

Fixed

  • Flashpoint directory is mounted read-write by default. The shipped compose files mounted it :ro, which silently failed every write the app makes: downloaded-state flags in flashpoint.sqlite, downloaded game data under Data/Games, and metadata sync. If you copied an earlier compose file, drop the :ro suffix.
  • Image preloads are cancelled when a request is abandoned, so scrolling a large library no longer queues work for images that left the viewport.

Added

  • A metadata sync can optionally download game images afterwards.

Performance

  • Heavy read-only queries run in SQLite worker threads (DB_WORKER_COUNT, default 4). better-sqlite3 is synchronous, and a filter-options query over a slow mount was measured blocking the entire server for 167 seconds — /health timed out along with everything else.
  • UV_THREADPOOL_SIZE is set to 16 in the image. libuv's default of 4 throttled every image and ZIP read in the process, which dominates on network storage.

[1.0.35] — 2026-08-12

Added

  • Admins can choose the metadata source used for syncs.

Fixed

  • Ruffle installs in the background and into the directory that is actually served (frontend/dist/ruffle). Startup previously awaited the download, so the port stayed closed for minutes on slow storage — indefinitely if the download stalled — and a fresh container re-downloaded the Ruffle it already shipped.
  • Locally served images are cached and no longer block on stat.
  • npm test runs the backend suite once instead of entering watch mode.

[1.0.34] — 2026-08-11

Fixed

  • Auth cookies are marked Secure per connection, not per NODE_ENV. Production builds previously always set Secure, and browsers discard such a cookie when it arrives over plain HTTP — logging LAN users straight back out after login. The new default, COOKIE_SECURE=auto, decides per request based on TLS (reading X-Forwarded-Proto when behind a proxy), so one deployment can serve HTTP on a LAN and HTTPS through a proxy at once. Set COOKIE_SECURE=true if your TLS-terminating proxy does not send that header.

Documentation

  • README carries a minimal working .env example and refreshed sections.

[1.0.33] — 2026-08-11

Fixed

  • CORS origins are normalized and same-origin requests are always allowed. With the UI and API on one origin, most deployments no longer need DOMAIN at all. DOMAIN also accepts a comma-separated list, and explicit default ports (:80, :443) match the port-less origin a browser actually sends.
  • The dev compose file passes FLASHPOINT_PATH=/data/flashpoint explicitly.

Performance

  • Downloaded filter options are cached indefinitely.

[1.0.32] — 2026-08-11

Breaking

  • Single image deployment. The backend and frontend images were collapsed into darkraise/flashpoint-web, which serves the API, game content, and the built UI from one Express process on port 3100. The frontend service, its nginx config and entrypoint, and the per-service Dockerfiles are gone; the release workflow builds one image instead of a two-service matrix. See Upgrading from 1.0.31.
  • API_PORT, BACKEND_HOST, and BACKEND_PORT were removed. Map a host port with WEB_PORT instead.

Added

  • Downloaded games page, filter, and settings: browse games whose data is already on disk, with the filter encoded in the URL and downloaded state reconciled from disk at startup.
  • 5-star game rating system.
  • Setting to toggle the "I'm Feeling Lucky" button.

Security

  • The SPA-oriented CSP was ported from nginx into Helmet and hardened, dropping unsafe-inline and unsafe-eval. The strict API-only policy still applies when SERVE_FRONTEND is off.

Fixed

  • Guest browsing survives auth-only 401s instead of falling into a redirect loop (thanks @MaxfieldKassel).
  • Invalid SQLITE_CACHE_SIZE and SQLITE_MMAP_SIZE values fall back to their defaults instead of becoming NaN.