bottleneckcrm/frontend/Dockerfile
vickytechkey af1d160464
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 35s
Revert "Convert frontend to React JS and add S3 deployment workflows"
This reverts commit e10f9829ee.
2026-10-24 14:42:05 +05:30

87 lines
3.8 KiB
Docker

# syntax=docker/dockerfile:1
#
# One file, two outputs: the `dev` stage docker-compose runs, and the default
# `runtime` stage a release publishes. They were going to be two files, which is
# how the pnpm pin in here already drifted from the one in .github/workflows
# (11.17.0 against 10). A single base stage means there is one version to bump.
#
# RUNTIME CONFIGURATION
#
# Every value this app reads from the environment is declared in src/env.js and
# read per process at startup, not at build time. So one image serves any
# deployment and nothing below bakes a hostname in:
#
# PUBLIC_DJANGO_API_URL required. The Django API origin, no trailing /api.
# PUBLIC_SENTRY_DSN optional. Unset disables Sentry (`enabled: !!dsn`).
# PORT defaults to 3000. HOST already defaults to 0.0.0.0.
# PROTOCOL_HEADER only for plain http. See below.
# BODY_SIZE_LIMIT set to 6M below. adapter-node 6 defaults to 512K, which
# would refuse a spreadsheet import (the API caps files
# at 5 MB) with a plain-text 413.
#
# The origin check is the one that fails quietly. SvelteKit compares the Origin
# header of every form POST with the origin the server thinks it serves, which
# adapter-node 6 builds from the request's Host header and the scheme `https`.
# (The ORIGIN variable that used to override this was removed in SvelteKit 3,
# so setting it does nothing.) Behind a TLS proxy that forwards Host, such as
# nginx with `proxy_set_header Host $host`, that is already right. If users
# reach the app over plain http, set PROTOCOL_HEADER=x-forwarded-proto and have
# the proxy send that header. Otherwise every form POST is answered `403
# Cross-site POST form submissions are forbidden` while GETs still work, so the
# app looks healthy while all its form actions reject.
FROM node:24-slim AS base
# Pinned so image builds are reproducible.
RUN corepack enable && corepack prepare pnpm@11.17.0 --activate
WORKDIR /app
# Manifests only, so this layer caches on the lockfile rather than on source.
# pnpm-workspace.yaml carries the install settings (onlyBuiltDependencies,
# allowBuilds) that pnpm 10+ no longer reads from package.json. Without it
# @sentry/cli's postinstall is silently skipped and its binary never lands.
FROM base AS deps
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
RUN pnpm install --frozen-lockfile
# The compose target. Source and node_modules arrive as mounts, so this stage
# deliberately copies no source of its own.
FROM deps AS dev
EXPOSE 5181
CMD ["pnpm", "dev", "--host", "0.0.0.0"]
FROM deps AS builder
COPY . .
RUN pnpm build
# Runtime dependencies only. `build/` is NOT self-contained: SvelteKit keeps
# instrumentation.server.js external so OpenTelemetry can hook the process
# before anything else loads, and it imports @sentry/sveltekit by name. Without
# this stage the server dies at boot on ERR_MODULE_NOT_FOUND.
#
# --ignore-scripts because the `prepare` script shells out to svelte-kit, which
# is a devDependency and therefore absent here. package.json already tolerates
# that with `|| echo ''`; skipping it outright keeps the failure out of the log.
FROM base AS prod-deps
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
FROM node:24-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV BODY_SIZE_LIMIT=6M
COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app/build ./build
# The server reads its own name and version from here on startup.
COPY --chown=node:node package.json ./
# Present in the node images already. The server binds 3000, so it needs no
# privileged port and no reason to be root.
USER node
EXPOSE 3000
CMD ["node", "build/index.js"]