# build stage FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS build-stage # Set environment variables for non-interactive npm installs ENV NPM_CONFIG_LOGLEVEL=warn ENV CI=true RUN apk add --update python3 make g++\ && rm -rf /var/cache/apk/* WORKDIR /app COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY patches patches COPY stubs stubs RUN npm install -g pnpm@12.6.0 && pnpm i --ignore-scripts --frozen-lockfile COPY . . # Deliberately no BASE_URL here: the bundle is built path-agnostic (relative asset URLs # plus a `` in index.html) so that one image can be served from any # subpath. The path is a runtime setting instead -- see the BASE_URL environment variable # on the production stage below. ARG VITE_AVAILABLE_LOCALES ENV VITE_AVAILABLE_LOCALES=${VITE_AVAILABLE_LOCALES} ENV VITE_VERCEL_ENV=production # Precompress hashed assets once instead of compressing each response at runtime. RUN pnpm build \ && find dist/assets -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.css' -o -name '*.wasm' \) \ -exec gzip -9 -n -k {} + # production stage FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:f5116ed4606f3eb015dd4a8e28cb9b351476dc0ad4bc6e9f93b19cec130cdf03 AS production-stage LABEL maintainer="ShareVB " \ org.opencontainers.image.authors="ShareVB " LABEL org.opencontainers.image.source=github.com/sharevb/it-tools ENV VITE_VERCEL_ENV=production # The path the app is served under. Override it at run time -- `docker run -e # BASE_URL=/it-tools/`, `environment: BASE_URL: /it-tools/` in a compose file -- and nginx # rewrites the `` in index.html on the way out; nothing is baked in. The build # arg only moves the default, for anyone who prefers to ship an image that is preconfigured # for a subpath. See docker-entrypoint.d/18-resolve-base-url.envsh and nginx.conf. ARG BASE_URL=/ ENV BASE_URL=${BASE_URL} COPY --from=build-stage /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/templates/default.conf.template COPY docker-entrypoint.d/ /docker-entrypoint.d/ ENV PORT=8080 # nginx defaults to `worker_processes auto`, which counts the host's cores and # ignores the container's cpu limit. On a large host that is hundreds of # workers in a small container: `docker run -m 192m` is enough to get the # image OOM-killed on startup. Let the entrypoint size the pool from the cgroup # cpu quota instead. Uncapped containers still get one worker per core. ENV NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE=1 # Render the template once here as well. The entrypoint normally re-renders it # at startup, so a custom $PORT keeps working, but it cannot do so on a # read-only filesystem (see docker-entrypoint.d/19-skip-envsubst-if-readonly.envsh). # Without this the image would fall back to the stock welcome config from the # base image: no SPA fallback, so deep links 404 on refresh, and no COOP/COEP, # so the WebAssembly-backed tools stop working. # Sourcing the entrypoint's own resolver keeps the baked-in BASE_URL normalised and # validated exactly the way a runtime one would be. RUN . /docker-entrypoint.d/18-resolve-base-url.envsh \ && envsubst '${PORT} ${BASE_URL} ${BASE_URL_REGEX} ${BASE_URL_NO_SLASH_REGEX}' \ < /etc/nginx/templates/default.conf.template \ > /etc/nginx/conf.d/default.conf EXPOSE $PORT CMD ["nginx", "-g", "daemon off;"]