# ============================================================================= # Cipher Barcode Studio — production image # Base: nginx 1.30.1 on Alpine 3.23 (unprivileged variant — runs as UID 101) # ============================================================================= FROM nginxinc/nginx-unprivileged:1.30.1-alpine3.23-slim # OCI labels for traceability LABEL org.opencontainers.image.title="Cipher Barcode Studio" \ org.opencontainers.image.description="Static web app for generating barcodes in 22 symbologies and exporting JPG/PNG" \ org.opencontainers.image.version="1.0.0" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.source="https://example.com/cipher-barcode-studio" # The unprivileged image runs as user 101 (nginx). All file ops use --chown. USER root # Apply the latest Alpine security patches at build time, then strip the cache RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/* # Replace the default nginx config with our hardened one COPY --chown=nginx:nginx nginx/nginx.conf /etc/nginx/nginx.conf # Copy the static site COPY --chown=nginx:nginx web/ /usr/share/nginx/html/ # Make sure runtime dirs the unprivileged user needs are writable RUN mkdir -p /tmp/client_body /tmp/proxy /tmp/fastcgi /tmp/uwsgi /tmp/scgi \ && chown -R nginx:nginx /tmp/client_body /tmp/proxy /tmp/fastcgi /tmp/uwsgi /tmp/scgi /var/log/nginx \ && chmod -R 755 /usr/share/nginx/html # Drop back to the non-root user for runtime USER nginx # Internal port — unprivileged nginx listens on 8080 EXPOSE 8080 # Healthcheck driven by /healthz from nginx.conf HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --quiet --tries=1 --spider http://127.0.0.1:8080/healthz || exit 1 CMD ["nginx", "-g", "daemon off;"]