Summary
Dockerfile is four lines and two of them are worth tightening:
FROM nginx:1.27-alpine AS runtime
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/marketplace.conf
COPY dist /usr/share/nginx/html
-
Runs as root. The stock nginx image starts its master process as root (workers drop to nginx). The container serves purely static files on port 8080 and needs no privileged port, so there is no reason for it. Use nginxinc/nginx-unprivileged:1.27-alpine (already listens on 8080 and runs as UID 101), or add the usual chown + USER nginx + pid/temp-path adjustments.
-
Base image pinned by tag, not digest. Every GitHub Action in .github/workflows/ is pinned to a SHA — good, and deliberate. The container base is not, so 1.27-alpine silently changes under the deployment. Pin it: FROM nginx:1.27-alpine@sha256:… and let Dependabot bump it (the dependencies label already exists).
-
dist/ is a build input from outside the image. COPY dist /usr/share/nginx/html means the image is only reproducible if whoever runs docker build ran the right npm run build variant first — and there is nothing stopping a stale or standalone-mode dist/ from being baked in. Either make it a multi-stage build (FROM node:24 AS build running npm ci && npm run build:headless), or add a build-time guard that fails when dist/ is missing/stale, and document the required mode. Note .dockerignore exists, so a multi-stage build would need it revisited.
-
Minor: no USER, no --no-cache concerns (nothing installed), and the HEALTHCHECK uses wget which is present in alpine — that part is fine.
Adding a Trivy/Grype scan of the built image to CI would catch base-image CVEs, complementing the existing npm audit job which only covers JS dependencies.
Summary
Dockerfileis four lines and two of them are worth tightening:Runs as root. The stock
nginximage starts its master process as root (workers drop tonginx). The container serves purely static files on port 8080 and needs no privileged port, so there is no reason for it. Usenginxinc/nginx-unprivileged:1.27-alpine(already listens on 8080 and runs as UID 101), or add the usualchown+USER nginx+pid/temp-path adjustments.Base image pinned by tag, not digest. Every GitHub Action in
.github/workflows/is pinned to a SHA — good, and deliberate. The container base is not, so1.27-alpinesilently changes under the deployment. Pin it:FROM nginx:1.27-alpine@sha256:…and let Dependabot bump it (thedependencieslabel already exists).dist/is a build input from outside the image.COPY dist /usr/share/nginx/htmlmeans the image is only reproducible if whoever runsdocker buildran the rightnpm run buildvariant first — and there is nothing stopping a stale or standalone-modedist/from being baked in. Either make it a multi-stage build (FROM node:24 AS buildrunningnpm ci && npm run build:headless), or add a build-time guard that fails whendist/is missing/stale, and document the required mode. Note.dockerignoreexists, so a multi-stage build would need it revisited.Minor: no
USER, no--no-cacheconcerns (nothing installed), and theHEALTHCHECKuseswgetwhich is present in alpine — that part is fine.Adding a Trivy/Grype scan of the built image to CI would catch base-image CVEs, complementing the existing
npm auditjob which only covers JS dependencies.