# ============================================ # WeLe Agentic AI — production image # # Text-only agent service. node:20-slim rather than alpine: several native # dependencies ship glibc binaries that will not load against musl. # ============================================ FROM node:20-slim AS deps WORKDIR /app COPY package*.json ./ # `npm ci` builds exactly the lockfile, so a deploy can never silently pick up # a different dependency tree than the one that was tested. RUN npm ci --omit=dev FROM node:20-slim WORKDIR /app # Run unprivileged. The base image already ships a `node` user. ENV NODE_ENV=production \ PORT=4000 COPY --from=deps /app/node_modules ./node_modules COPY package.json ./ COPY src/ ./src/ # A named volume is mounted over this in compose. Docker seeds a NEW volume # from the image path — including ownership — so it must exist here owned by # `node`, or the unprivileged process gets EACCES on first write. RUN mkdir -p /app/storage/artifacts && chown -R node:node /app/storage USER node EXPOSE 4000 # Compose owns restart policy; this is the in-container liveness signal. HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||4000)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["node", "src/server.js"]