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/ ./ RUN npm run build 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"]