# Walled-store subsystem: the app + a REAL headful Chrome under a virtual display, # streamed to the user via noVNC so a human can clear a Cloudflare challenge on demand. # See WALLED-STORES.md. Run on a home box (residential IP), never a big datacenter. # # It is intentionally a fat single-container image for a personal tool. # # Node 24 LTS: matches the dev box (fnm lts-latest) AND bundles npm 11 — web/package-lock.json # is npm-11-shaped (nested @spartan-ng/cli peer pins) and node:22's npm 10 rejects it on `ci`. FROM node:24-bookworm-slim # Real Google Chrome (NOT chromium-headless) + virtual display + VNC + noVNC bridge. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates fonts-liberation wget gnupg dumb-init \ xvfb x11vnc novnc websockify procps \ libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 \ libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 libpangocairo-1.0-0 \ && wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google.gpg \ && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ && apt-get update && apt-get install -y --no-install-recommends google-chrome-stable \ && rm -rf /var/lib/apt/lists/* ENV CHROME_BIN=/usr/bin/google-chrome-stable \ DISPLAY=:99 \ BROWSER_URL=http://127.0.0.1:9222 \ NOVNC_URL=http://127.0.0.1:8080/vnc.html?autoconnect=1 \ HOST=0.0.0.0 \ PORT=3000 WORKDIR /app # Install root + web deps (dev deps needed: the app runs via tsx and builds the Angular UI). COPY package*.json ./ RUN npm ci COPY web/package*.json ./web/ RUN npm --prefix web ci COPY . . RUN npm run build:web EXPOSE 3000 8080 ENTRYPOINT ["dumb-init", "--"] CMD ["/app/docker/entrypoint.sh"]