# Trip Plan — production image # node:20-slim (Debian/glibc) so better-sqlite3 prebuilt binaries load correctly. FROM node:20-slim ENV NODE_ENV=production \ PORT=3000 \ DATA_DIR=/app/data WORKDIR /app # Install production dependencies first for better layer caching. COPY package*.json ./ RUN npm ci --omit=dev && npm cache clean --force # Application code. COPY src/ ./src/ COPY public/ ./public/ # Data directory for the SQLite file (volume-mounted in compose). # Owned by the unprivileged `node` user that ships with the base image. RUN mkdir -p /app/data && chown -R node:node /app # The entrypoint starts as root only to chown a bind-mounted DATA_DIR # (Docker creates host dirs root-owned), then drops to `node` via setpriv. COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 3000 # Any HTTP response (including 401) from the API means the server is up. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://localhost:3000/api/auth/me').then(()=>process.exit(0)).catch(()=>process.exit(1))" CMD ["node", "src/server/index.js"]