FROM node:20-bookworm-slim AS frontend-build
WORKDIR /app/frontend
COPY frontend/package.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build

FROM node:20-bookworm-slim AS backend-build
WORKDIR /app/backend
COPY backend/package.json ./
RUN npm install
COPY backend/ ./
# There is deliberately no `COPY .git` here. It was tried in #233 and broke
# every Portainer deploy with `"/.git": not found` — Portainer's build context
# does not contain the repository history, whatever a local `docker build`
# suggests. That was the version stamp becoming the thing that stopped a
# deploy, which is the one outcome it must never be (#235).
#
# The consequence is that `commit` reads "unknown" in any environment Portainer
# builds. `builtAt` is still real, and is the half that matters most here:
# Portainer already reports which commit it cloned, but it cannot tell you
# whether the running container is actually that build. A build time can.
#
# writeBuildInfo runs against the compiled output, so it has to follow tsc, and
# it warns rather than fails when it finds no .git.
# Passed in rather than discovered. Whoever builds knows the commit; the build
# does not go looking for it, which is what broke every deploy when it did
# (#235) and what building in CI did not fix (#237). Empty by default, so a
# build that does not pass one behaves exactly as before — Portainer cannot
# supply it and still deploys, which is the property that must not regress.
#
#   docker build --build-arg GIT_COMMIT="$(git rev-parse --short HEAD)" .
ARG GIT_COMMIT=
RUN GIT_COMMIT="$GIT_COMMIT" npm run build && GIT_COMMIT="$GIT_COMMIT" node dist/writeBuildInfo.js

FROM node:20-bookworm-slim
WORKDIR /app
COPY --from=backend-build /app/backend/package.json ./
RUN npm install --omit=dev
COPY --from=backend-build /app/backend/dist ./dist
COPY --from=backend-build /app/backend/migrate.js ./migrate.js
COPY --from=backend-build /app/backend/migrations ./migrations
COPY --from=frontend-build /app/frontend/dist ./public
ENV NODE_ENV=production
EXPOSE 3000
# Migrations run before the app serves, so deployed code can never be ahead of
# the database schema. Previously this was a separate, manual
# `docker exec ... node migrate.js up` that was easy to forget — and forgetting
# it meant every item query referenced tables that did not exist yet, which
# surfaced as an empty storefront rather than an error.
#
# `&&` matters: migrate.js exits non-zero on failure, so a bad migration stops
# the container instead of letting it serve against a half-migrated schema.
CMD ["sh", "-c", "node migrate.js up && node dist/server.js"]