# ─── Build base (Alpine — small, used for npm install + tsc) ────────────────── FROM node:22-alpine AS base RUN apk add --no-cache libc6-compat WORKDIR /app # ─── Dependencies (include devDeps — needed for tsc, next build, etc.) ──────── FROM base AS deps COPY package.json package-lock.json* tsconfig.base.json ./ COPY apps/web/package.json ./apps/web/ COPY worker/package.json ./worker/ COPY shared/package.json ./shared/ # NODE_ENV must NOT be production here — devDependencies (tsc, tsx, etc.) are required to build RUN npm install --workspace=apps/web --workspace=worker --workspace=shared # ─── Shared build ──────────────────────────────────────────────────────────── FROM deps AS shared-build COPY shared/ ./shared/ RUN npm run build --workspace=shared # ─── Next.js build ────────────────────────────────────────────────────────── FROM shared-build AS web-build COPY apps/web/ ./apps/web/ RUN npm run build --workspace=apps/web # ─── Worker build ────────────────────────────────────────────────────────── FROM shared-build AS worker-build COPY worker/ ./worker/ RUN npm run build --workspace=worker # ─── Web runtime (Alpine) ───────────────────────────────────────────────────── FROM node:22-alpine AS web RUN apk add --no-cache libc6-compat RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs WORKDIR /app ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY --from=web-build /app/apps/web/.next ./apps/web/.next COPY --from=web-build /app/apps/web/public ./apps/web/public COPY --from=shared-build /app/shared/dist ./shared/dist COPY shared/package.json ./shared/ COPY apps/web/package.json ./apps/web/ USER nextjs WORKDIR /app/apps/web EXPOSE 3000 # Use absolute path — WORKDIR is /app/apps/web but node_modules are at /app/node_modules CMD ["/app/node_modules/.bin/next", "start"] # ─── Valhalla worker (gis-ops Valhalla image + Node.js 22) ─────────────────── # This container runs both a BullMQ worker (build-valhalla jobs) AND the # valhalla_service HTTP server. It has valhalla_build_tiles and friends # pre-installed from the base image. Node.js is added for the BullMQ consumer. FROM ghcr.io/gis-ops/docker-valhalla/valhalla:latest AS valhalla-worker USER root RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates curl gnupg osmium-tool \ && mkdir -p /etc/apt/keyrings \ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \ > /etc/apt/sources.list.d/nodesource.list \ && apt-get update \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* WORKDIR /app ENV NODE_ENV=production # BullMQ and postgres are pure JS — no native add-ons — so Alpine-built # node_modules from the deps stage work on this Debian/glibc base. COPY --from=deps /app/node_modules ./node_modules COPY --from=worker-build /app/worker/dist ./worker/dist COPY --from=shared-build /app/shared/dist ./shared/dist COPY shared/package.json ./shared/ COPY worker/package.json ./worker/ # /data/osm is shared with the pipeline worker (osm_data volume). # Make it world-writable so the worker (UID 1001) can write PBF files here # regardless of which container initialises the Docker volume first. # valhalla mounts this volume :ro so it can never write here accidentally. RUN mkdir -p /data/osm /data/valhalla && chmod 1777 /data/osm ENTRYPOINT ["/bin/node"] CMD ["worker/dist/valhalla-main.js"] # ─── Worker runtime (Debian slim — osmium-tool + osm2pgsql are in apt) ──────── FROM node:22-slim AS worker RUN apt-get update && apt-get install -y --no-install-recommends \ osmium-tool \ osm2pgsql \ && rm -rf /var/lib/apt/lists/* RUN groupadd --system --gid 1001 nodejs && useradd --system --uid 1001 --gid nodejs workeruser WORKDIR /app ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY --from=worker-build /app/worker/dist ./worker/dist COPY --from=shared-build /app/shared/dist ./shared/dist COPY shared/package.json ./shared/ COPY infra/ ./infra/ COPY worker/package.json ./worker/ # Create data directories owned by workeruser so Docker named volumes # are initialized with the correct permissions on first run. RUN mkdir -p /data/osm /data/valhalla && chown -R workeruser:nodejs /data USER workeruser CMD ["node", "worker/dist/index.js"]