Docker Image & Container¶
How images are built¶
Frontend and backend are separate images, both built from the build:
blocks in docker-compose.yml; neither is pulled from an external
registry.
| Image | Dockerfile | Build context |
|---|---|---|
| Backend | iqvflex/backend/Dockerfile |
./iqvflex/backend |
| Frontend | dashboard/Dockerfile |
./dashboard |
Backend image¶
node:22-alpine based, multi-stage build (base → deps → runtime):
- The
depsstage copies onlypackage.json/package-lock.jsonand runsnpm ci --omit=devfor production dependencies (this layer is reused across rebuilds when only the source changes — fast rebuilds). - The
runtimestage copiesnode_modules,app.js,server.js,src/; the container does not run as root (USER node).EXPOSE 3001. - The in-container
HEALTHCHECKuses the same logic as the install/update scripts: a realHTTP 200+data.status === 'ok'(/health).
Frontend image¶
Two stages: node:22-alpine (build) for the build, nginx:1.27-alpine
(runtime) to serve it. The runtime image contains no Node.js or source
code (small, narrow attack surface).
- The
buildstage runsnpm ci(including devDependencies —vite/typescriptare needed for the build), thennpm run build(tsc && vite build) with theVITE_API_BASE_URLbuild-arg (empty in Docker). - The
runtimestage copies only thedist/output,nginx.confandproxy_params.conf.EXPOSE 80. - The in-container
HEALTHCHECK:wgetagainsthttp://127.0.0.1/__nginx_health(nginx's own endpoint, not proxied to the backend).
Production image approach¶
The same images are used in both development and production; production
hardening (read-only filesystem, closed ports, resource limits) is applied
not at the image level but through the docker-compose.prod.yml
override (see Docker Architecture).
Image tag / version logic¶
image: ${COMPOSE_PROJECT_NAME:-iqvflex}-backend:${VERSION:-latest}
image: ${COMPOSE_PROJECT_NAME:-iqvflex}-frontend:${VERSION:-latest}
If the VERSION environment variable is not set, images are built with
the latest tag. install.sh / update.sh read the repository root
VERSION file (canonical SemVer version, e.g. 1.0.0) and export it
as the VERSION environment variable — this is how the image tag stays in
sync with the repository version. Versions in dashboard/package.json and
iqvflex/backend/package.json are component-level and are not used in
the image tag.