Replace the host-port-published nginx service with one that joins an external Docker network and routes entirely through Traefik labels (HTTP->HTTPS redirect, TLS router, security-headers middleware), matching the pattern used by other Coolify-deployed projects. Add .env.example for TRAEFIK_ENABLED/TRAEFIK_HOST/NETWORK_NAME and document the new deploy flow and local-preview alternative in the README. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vpinball
Play real .vpx pinball tables in your browser. Drop in your own table or try the bundled demo —
no plugins, no native install. Powered by @valknar/vpinball-wasm,
a WebAssembly build of the real Visual Pinball engine
(real physics, real VBScript table scripting, real WebGL2 rendering).
Features
- Play any self-contained
.vpxtable — drag a file onto the landing page, or click to browse. - ROM support for real-hardware ("SS") tables — attach the table's PinMAME ROM
.zipalongside the.vpxupload; most tables don't need this. - A local table library — uploads are saved in the browser (IndexedDB) so you don't have to re-upload the same table (and its ROM) on your next visit. Nothing leaves your browser; there's no server-side storage.
- Demo mode — a bundled table plays instantly with no upload required.
- Fullscreen, a live FPS/stats readout, and a game info panel (table name, size, load time) in an in-game HUD styled after a pinball cabinet's coin door.
- Installable as a PWA — add it to your home screen or desktop; the engine is cached after your first game so it's available offline afterward.
- Deployable as a static site via Docker Compose — no Node server at runtime, just nginx serving prebuilt files.
Prerequisites
- Node.js ≥ 20, pnpm ≥ 11
- Docker + Docker Compose, for the containerized deploy
Install
pnpm install --frozen-lockfile --trust-lockfile
vpinball-wasm is published to a private Gitea npm registry
(https://dev.pivoine.art/api/packages/valknar/npm/) under the @valknar scope. .npmrc maps
that scope to the registry; the package is publicly readable, so no auth token is needed to
install it.
To bump the engine to a newer published version:
pnpm add @valknar/vpinball-wasm@<version>
Development
pnpm dev
pnpm dev copies the engine's build output into public/vendor/vpinball-wasm/ first (via the
predev script) — it's gitignored and regenerated from node_modules, so this only needs
pnpm install to have completed. Re-run pnpm run copy-engine-assets by hand if you update the
dependency without restarting the dev server.
The dev server runs at http://localhost:3000.
Build
pnpm build
Produces a fully static export in out/ (next.config.ts sets output: "export") — no Node
server needed to serve it.
Docker deploy
No secrets or registry authentication are required to build the image.
The compose file is set up for a Coolify/Traefik deployment: it joins an
external Docker network and routes entirely through Traefik labels, with no host port published.
Copy .env.example to .env and set:
TRAEFIK_ENABLED=trueTRAEFIK_HOST— the domain to route to this serviceNETWORK_NAME— the external Docker network Traefik and this service both join (Coolify provisions this automatically for projects deployed through it)
docker compose up -d --build
There's no local-only mode — without Traefik routing to it, the container isn't reachable from
the host. To preview the static build locally instead, run pnpm build and serve out/ with any
static file server (e.g. npx serve out).
PWA
The app can be installed from the browser's install prompt. A hand-rolled service worker
(public/sw.js) precaches the app shell; the engine's .wasm/.data files (~34 MB) are cached
on first visit to /play, not upfront, so installing the app doesn't force a large download
before anyone has actually played. After that first visit, offline reloads work.
When you ship a release that changes the app shell or updates the vendored engine build, bump the cache version so returning visitors actually get the update:
pnpm run sw:bump
Controls
| Key | Action |
|---|---|
| 4 | Insert coin |
| 1 | Start game |
| Enter | Launch ball |
| Left Shift | Left flipper |
| Right Shift | Right flipper |
On touch devices, an on-screen control overlay appears automatically once a table is running.
Limitations
Inherited from the underlying engine:
- Only self-contained
.vpxtables are supported — tables that reference an external.vbsscript override or aMusic/folder won't load correctly. - PinMAME (
VPinMAME.Controller) ROM-driven emulation runs, but on-screen DMD/backglass output and hands-on switch/solenoid/scoring behavior aren't yet confirmed end-to-end — see the engine's own README for the current state. No DOF, FlexDMD, or backglass (.directb2s) plugin support. ROM files are copyrighted; only supply ones you're legally entitled to use. - The ROM
gameNamePinMAME needs is derived from the uploaded zip's filename (e.g.hvymetal.zip→hvymetal) — rename the zip to match if a table doesn't recognize its ROM. - Single-threaded (no
SharedArrayBuffer) — performance depends on your device's single-core WebGL2 throughput. - Initial engine download is ~34 MB (cached after the first visit).
- Gamepad input and DMD rendering are implemented but not yet confirmed on real hardware/tables.
- Raw cabinet hardware input (real nudge/plunger boards) has no browser equivalent.
- The table library lives in the browser's IndexedDB storage — clearing site data removes it, and it doesn't sync across browsers/devices.
License
This app's own source has no license file yet — add one before distributing it. Separately, the
built output bundles the @valknar/vpinball-wasm engine and its default table, which redistribute
Visual Pinball source under a mixed legacy/GPLv3+ license (migrating file-by-file to GPLv3+
since October 2020) — see that package's own LICENSE for the authoritative terms before
distributing a build.