A static site is built like any other service: from a Dockerfile, on one of your servers, in a virtual machine. Then its site folder is copied out of the image and published, and nothing of it runs. What matters is which folder holds the site, and that its variables reach the build.
Build the site in one stage, and copy what it made into the last one:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_URL
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
LABEL smartalfred.static=/usr/share/nginx/html
nginx never runs here: the image only carries the files. Ending on it means the same Dockerfile still makes a working container anywhere else, like on your laptop. Just the files works too: FROM scratch, COPY --from=build /app/dist / and LABEL smartalfred.static=/. What Docker adds to every container (/.dockerenv, /etc/hosts and the like) is left out.
Each of its steps runs in a virtual machine, like every build here.
A static site has nothing running to read an environment, so its variables go to its build instead, as build args. The Dockerfile picks up the ones it wants with ARG (above, VITE_API_URL). Vite takes variables starting with VITE_, Next.js those starting with NEXT_PUBLIC_. An app that runs (a Next.js or Nuxt server) gets its variables when it starts, and those named like these (VITE_, NEXT_PUBLIC_, NUXT_PUBLIC_, PUBLIC_, REACT_APP_, VUE_APP_, GATSBY_, EXPO_PUBLIC_) at its build as well, the same way: changing one of them builds it again.
Everything in a static site is public, so a site's secrets aren't given to its build. A variable can point at another service in the project, as in VITE_API_URL=${{api.URL}}. That reference can read the other service's HOST, PORT, URL, NAME and variables, but never its secrets or a bucket's keys. A reference to one of those stays as it was written.
Changed variables are staged like any service's. Deploying them builds the site again.
| Label | Meaning |
|---|---|
smartalfred.static: /usr/share/nginx/html | A static site, served from that folder of the built image. |
smartalfred.static: "true" | A static site; the image's own LABEL smartalfred.static says which folder. |
smartalfred.spa: "true" | SPA mode. |
A static site is public whatever its ports: say. Its environment: becomes its variables, and so its build args.