# syntax=docker/dockerfile:1 # 卫戍协议:盟约 · production image (server + static client). Docs: docs/DEPLOY.md「Docker」. # # Code: GPL-3.0-or-later (LICENSE). Game art/audio is © Hypergryph / Yostar, not covered by the GPL, non-commercial use # only (NOTICE.md), and never part of the repository. Two ways to get it into a container: # A) download it while building (~250 MB, needs internet during the build): # docker build -t stronghold-protocol --build-arg FETCH_ASSETS=1 . # B) build without it and mount the host's copy (prepared with `node tools/setup.mjs` on the host): # docker build -t stronghold-protocol . # docker run -d --name stronghold -p 3000:3000 --restart unless-stopped \ # -v "$PWD/public/assets:/app/public/assets:ro" stronghold-protocol # (public/fonts, data/assets.json and data/local-assets.json are copied from the build context when present) # Without any art the game still runs with placeholder visuals. # # Data: the opt-in Skland account binding (SP_SKLAND=1, docs/skland.md) is the only runtime state: one SQLite file of # account credentials (SP_SKLAND_DB). It must survive a container restart, so it belongs on a volume — this image # creates /app/skland-data and hands it to `node` below, and docker-compose.yml mounts the named volume `skland-data` # there with SP_SKLAND_DB=/app/skland-data/skland.db. There is deliberately no `VOLUME` instruction: an anonymous # volume would make `docker run` silently accumulate credentials a host cannot find, so the mount is the caller's # explicit choice (`-v skland-data:/app/skland-data`). The default (data/skland.db, inside the container) is only for # a throwaway run. The database is a secret: it holds account credentials, it sits outside every static mount, and it # must never be committed, served over HTTP or copied around unencrypted. # # Run: docker run -d --name stronghold -p 3000:3000 --restart unless-stopped stronghold-protocol # Env: PORT (3000), HOST (0.0.0.0), SP_COMBAT (client|server), SP_VERIFY (off|sample|all), TRUST_PROXY (auto|1|0), DEBUG, # SP_SKLAND (off; 1 turns the Skland account binding on), SP_SKLAND_DB (data/skland.db) ARG NODE_IMAGE=node:22-alpine # ---- 1. production dependencies (cached while package*.json are unchanged) ------------------------- FROM ${NODE_IMAGE} AS deps WORKDIR /app COPY package.json package-lock.json ./ # --ignore-scripts: the postinstall (tools/vendor.mjs) runs in the next stage, once the sources are there RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force # ---- 2. vendor libs + optional art download --------------------------------------------------------- FROM deps AS build ARG FETCH_ASSETS=0 COPY shared ./shared COPY server ./server COPY tools ./tools COPY data ./data COPY public ./public COPY docs/research ./docs/research RUN node tools/vendor.mjs \ && if [ "$FETCH_ASSETS" = "1" ]; then \ node tools/fetch-assets.mjs || echo "WARNING: art download incomplete; the image falls back to placeholder art"; \ fi \ && rm -rf .cache # ---- 3. runtime --------------------------------------------------------------------------------------- FROM ${NODE_IMAGE} ENV NODE_ENV=production \ PORT=3000 \ HOST=0.0.0.0 WORKDIR /app COPY --from=deps /app/package.json ./package.json COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/shared ./shared COPY --from=build /app/server ./server COPY --from=build /app/data ./data COPY --from=build /app/public ./public # research tables: read by server/sim/nodeData.js as a fallback COPY --from=build /app/docs/research ./docs/research # the Skland credential database's directory (SP_SKLAND_DB): created before USER node and owned by it, so a fresh named # volume inherits a writable mount point and the unprivileged user can create the .db and its -wal / -shm side files RUN mkdir -p /app/skland-data && chown node:node /app/skland-data USER node EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD wget -q -O /dev/null "http://127.0.0.1:${PORT}/healthz" || exit 1 CMD ["node", "server/index.js"]