# Build frontend FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci --legacy-peer-deps COPY frontend/ ./ RUN npm run build # Build backend FROM node:20-alpine AS backend-builder WORKDIR /app/backend COPY backend/package*.json ./ RUN npm ci COPY backend/ ./ RUN npm run build # Final image FROM node:20-alpine RUN apk add --no-cache tini WORKDIR /app # Create a non-root user RUN addgroup -S appgroup && adduser -S appuser -G appgroup # Copy built backend COPY --from=backend-builder /app/backend/dist ./dist COPY --from=backend-builder /app/backend/package*.json ./ # Copy built frontend COPY --from=frontend-builder /app/frontend/dist/frontend ./public # Install production dependencies only RUN npm ci --omit=dev # Create a startup script to serve both frontend and backend COPY --chmod=755 < /app/public/env.js echo "window.env = {" >> /app/public/env.js env | grep DOCKER_ | sed 's/\(.*\)=\(.*\)/ \1: "\2",/' >> /app/public/env.js echo "};" >> /app/public/env.js # Start the server node dist/index.js EOF # Set ownership RUN chown -R appuser:appgroup /app USER appuser EXPOSE 3000 ENTRYPOINT ["/sbin/tini", "--"] CMD ["/app/start.sh"]