# =========================================== # MynetworK - Node 22 Alpine (OPTIMIZED MULTI-STAGE) # =========================================== # Multi-arch: Buildx sets TARGETPLATFORM (amd64/arm64/arm/v7) per build. # Builder uses TARGETPLATFORM so native modules (e.g. better-sqlite3) are compiled for the target arch. ARG TARGETPLATFORM ARG BUILDPLATFORM # ---------- Stage 1: Build (with build tools) ---------- # Official node image via Google's Docker Hub mirror: Docker Hub rate-limits # anonymous pulls per IP, which regularly fails CI on shared GitHub runners. FROM mirror.gcr.io/library/node:22-alpine AS builder WORKDIR /app # Required build tools to compile better-sqlite3 and other native modules. # These tools are not present in the final image. RUN apk add --no-cache python3 make g++ # Install all dependencies (including devDependencies) only to compile native modules. # devDependencies are removed in the final image (see npm prune --production below). # Layer caching: package*.json rarely changes, so npm ci is cached across builds. # Cache is keyed per TARGETPLATFORM to avoid ETXTBSY race between amd64/arm64 esbuild binaries. COPY package*.json ./ ARG TARGETPLATFORM RUN --mount=type=cache,target=/root/.npm,id=npm-${TARGETPLATFORM} npm ci # Copy config files first (rarely change = better cache hits) COPY tsconfig.json vite.config.ts index.html postcss.config.js tailwind.config.js ./ # Copy source and build # Separate COPY for server and src to maximize layer reuse COPY server ./server COPY src ./src COPY public ./public COPY scripts ./scripts RUN npm run build # Prepare production node_modules (no devDependencies but with compiled binaries). # Keep compiled better-sqlite3 binaries and remove only devDependencies. # npm prune --production removes devDependencies but keeps compiled binaries. RUN npm prune --production && npm cache clean --force # ---------- Stage 2: Runtime (slim final image) ---------- # Re-declare so this stage gets Buildx TARGETPLATFORM (global ARG is not available after first FROM) ARG TARGETPLATFORM # Use TARGETPLATFORM so the runtime base matches the built arch (required for HA / Raspberry) FROM mirror.gcr.io/library/node:22-alpine WORKDIR /app # Runtime tools only (no build tools). # su-exec: required for entrypoint script (switch root → node). # iputils-ping: required for network scan (ping command). # iproute2: required for network scan (ip neigh for MAC detection). # samba-common: provides nmblookup for NetBIOS/SMB hostname resolution. # curl: required to download IEEE OUI vendor database from standards-oui.ieee.org (with plugin fallback on failure). # Note: getent is not available on Alpine (musl libc); we use direct /etc/hosts reading. # Note: libpcap/libpcap-dev removed as arp-scan is optional (fallback after ip neigh). # If arp-scan is needed, it must be compiled in the build stage. # nmap: for port scanning (option "Scan ports after each full scan"). # arping: for direct ARP-based MAC address resolution (reliable in native and host network mode). # Upgrade base packages first to fix known CVEs (e.g. zlib CVE-2026-27171) RUN apk upgrade --no-cache && \ apk add --no-cache su-exec iputils-ping iproute2 samba-common curl nmap arping # Create data directory with correct permissions RUN mkdir -p /app/data && chown -R node:node /app # Copy entrypoint script (requires su-exec) COPY docker-entrypoint.sh /app/docker-entrypoint.sh RUN chmod +x /app/docker-entrypoint.sh # Entrypoint to fix permissions on startup ENTRYPOINT ["/app/docker-entrypoint.sh"] # Copy compiled node_modules from build stage (native binaries already compiled). # Avoids recompiling better-sqlite3 in the final image. # Binaries are compiled for the target architecture in the build stage. COPY --chown=node:node --from=builder /app/node_modules ./node_modules # Copy package.json for reference (required by some dependencies) COPY --chown=node:node package*.json ./ # Built frontend COPY --chown=node:node --from=builder /app/dist ./dist # Backend TypeScript (run by tsx) COPY --chown=node:node --from=builder /app/server ./server COPY --chown=node:node --from=builder /app/tsconfig.json ./ # CLI scripts (e.g. mcp-token.ts), run inside the container via # `docker compose exec mynetwork node_modules/.bin/tsx scripts/mcp-token.ts` # (npm is removed from the runtime image below, so tsx is invoked directly) COPY --chown=node:node --from=builder /app/scripts ./scripts ENV NODE_ENV=production ENV PORT=3000 # FREEBOX_TOKEN_FILE and FREEBOX_HOST should be set at runtime via docker-compose or environment variables # This avoids security warnings about sensitive data in Dockerfile # Healthcheck with wget (already available in base Alpine) HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD wget -q --spider http://127.0.0.1:${PORT}/api/health || exit 1 EXPOSE 3000 # Remove npm/npx/corepack: unused at runtime (CMD invokes tsx directly, see below, # and CLI scripts like mcp-token are run via tsx too, see README). Node's base image # bundles npm with its own vendored deps (tar, brace-expansion, sigstore) that lag # behind upstream fixes; dropping npm entirely removes that unused attack surface # instead of waiting on a patched node:22-alpine. RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack \ /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack # TS runtime (tsx runs TypeScript files directly) CMD ["node_modules/.bin/tsx", "server/index.ts"]