anvilsign in

collin/mahjong

1# The whole production stack is one process: the built game out of dist/, and
2# the room relay on /ws of the same origin. So the image is a node runtime, the
3# bundle, and `server/`.
4#
5# No state. Rooms live in memory, and a restart drops them — the next visitor
6# on an old link recreates the room empty and a host mid-game republishes its
7# table on the next move. There is nothing here to mount a volume for.
8
9# ---- build: typecheck, bundle ------------------------------------------
10FROM node:24-alpine AS builder
11WORKDIR /app
12
13COPY package.json package-lock.json ./
14RUN npm ci
15
16COPY . .
17RUN npm run build
18
19# The runtime needs `ws` and nothing else the dev toolchain drags in.
20RUN npm prune --omit=dev
21
22# ---- runtime ------------------------------------------------------------
23FROM node:24-alpine AS runtime
24WORKDIR /app
25
26RUN addgroup -S app && adduser -S app -G app
27
28COPY --from=builder --chown=app:app /app/node_modules ./node_modules
29COPY --from=builder --chown=app:app /app/dist ./dist
30COPY --from=builder --chown=app:app /app/server ./server
31COPY --from=builder --chown=app:app /app/package.json ./package.json
32
33ENV NODE_ENV=production
34ENV HOST=0.0.0.0
35ENV PORT=3000
36
37EXPOSE 3000
38USER app
39
40# server/ is TypeScript and node strips the types itself — there is no build
41# step for it and nothing generated to keep in sync with the source.
42CMD ["node", "--no-warnings=ExperimentalWarning", "server/index.ts"]