# node:22-alpine as of 2026-02-22 FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS build WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY . . RUN yarn build # nginx:stable-alpine as of 2026-02-22 FROM nginx@sha256:15e96e59aa3b0aada3a121296e3bce117721f42d88f5f64217ef4b18f458c6ab # Remove default config RUN rm /etc/nginx/conf.d/default.conf # Custom nginx config: real_ip from RFC1918, access_log to stdout COPY <<'EOF' /etc/nginx/conf.d/netwatch.conf server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Trust RFC1918 reverse proxies for X-Forwarded-For set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on; # Access log to stdout (Docker best practice) access_log /dev/stdout combined; error_log /dev/stderr warn; location / { try_files $uri $uri/ /index.html; } # Cache static assets aggressively location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } EOF COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80