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"]