# STAGE 1 — the test gate. `node test.js` drives the real game modules headless; if the state # machine breaks, this stage fails, the image never builds, and the deploy never ships. The final # stage COPYs the marker so BuildKit cannot skip this stage as unused. FROM node:22-alpine AS test WORKDIR /app COPY test.js /app/ COPY src /app/src RUN node test.js && touch /tests-passed FROM python:3.12-alpine # Was nginx:alpine. The leaderboard needs one process that can WRITE, and nginx is not one. # Everything the nginx conf was doing here — no-cache so a deploy is picked up on a plain reload, # and a real MIME type for the manifest, without which Chrome refuses to install the homescreen # app — is a few lines in server.py. Standard library only: no pip, no requirements file. # ponytail: switch to hashed filenames if traffic ever matters. WORKDIR /app COPY --from=test /tests-passed /tmp/tests-passed COPY index.html manifest.webmanifest manifest-stg.webmanifest server.py /app/ COPY src /app/src COPY assets /app/assets COPY promo /app/promo # THE SCORES LIVE HERE AND THIS MUST BE A PERSISTENT VOLUME. Without one the json sits in the # container's writable layer and every deploy silently resets the leaderboard to empty — which # looks exactly like the feature working until the first time you push. ENV SPLAT_DATA=/data VOLUME /data EXPOSE 80 CMD ["python", "-u", "server.py"]