From e1a02a6d6958a571e8bdae29ac6fbcc6e00a9ca5 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Sun, 4 Jan 2026 00:33:47 +0000 Subject: [PATCH] perf: build frontend once on runner instead of in Docker MAJOR PERFORMANCE IMPROVEMENT: Reduced frontend Docker build time from ~20 minutes to ~30 seconds by building the SPA once on the runner instead of twice (amd64 + arm64) in Docker. Changes: 1. Release workflow now builds frontend on CI runner (native, fast) 2. Frontend artifact uploaded and downloaded in release job 3. Dockerfile.frontend simplified to just copy pre-built files 4. Multi-arch Docker build is now just copying files into nginx Before: - Docker builds frontend 2x (amd64 + arm64 with QEMU emulation) - Each: pnpm install + pnpm build = ~10 min per arch - Total: ~20 minutes for frontend image After: - Build frontend 1x on runner = ~2 min (native) - Docker just copies files = ~30 sec (both architectures) - Total: ~2.5 minutes for frontend image Impact: - 8x faster frontend builds - Total release time reduced from ~25 min to ~7 min - Lower resource usage (no QEMU emulation) Files changed: - .github/workflows/release.yaml: Enable node build - Dockerfile.frontend: Remove build stage, expect pre-built files - .goreleaser.yaml: Copy frontend/dist instead of full source --- .github/workflows/release.yaml | 6 ++++++ .goreleaser.yaml | 3 ++- Dockerfile.frontend | 22 ++++------------------ 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5cfdb3a..0afbe37 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,6 +23,12 @@ jobs: uses: lukaszraczylo/shared-actions/.github/workflows/go-release-cgo.yaml@main with: go-version: "1.25" + # Enable frontend build + node-enabled: true + node-version: "20" + node-build-script: "cd frontend && npm install -g pnpm && pnpm install --frozen-lockfile && pnpm run build" + node-output-path: "frontend/dist" + node-cache-dependency-path: "frontend/pnpm-lock.yaml" secrets: inherit benchmark: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 81eeefb..7d57cd9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -155,6 +155,7 @@ dockers_v2: - config.yaml.example # 2. Website - Frontend Dashboard + # Note: Frontend is pre-built on CI runner and injected via frontend/dist - id: gohoarder-frontend images: - ghcr.io/lukaszraczylo/gohoarder-frontend @@ -175,7 +176,7 @@ dockers_v2: - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.revision={{ .FullCommit }}" extra_files: - - frontend + - frontend/dist # 3. Scanning Engine - Background scanner worker - id: gohoarder-scanner diff --git a/Dockerfile.frontend b/Dockerfile.frontend index c6e8a6b..42e125b 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -1,29 +1,15 @@ # Website - Frontend Dashboard with integrated reverse proxy # Combines frontend serving and backend proxy (previously separate gateway) -# Build stage -FROM node:20-alpine AS builder +# EXPECTS: Pre-built frontend files in frontend/dist/ directory -WORKDIR /build - -# Copy frontend source -COPY frontend/package.json frontend/pnpm-lock.yaml ./ -COPY frontend/ ./ - -# Install pnpm and dependencies -RUN npm install -g pnpm && \ - pnpm install --frozen-lockfile - -# Build the frontend -RUN pnpm run build - -# Production stage FROM nginx:alpine # Install envsubst for runtime configuration RUN apk add --no-cache gettext -# Copy built frontend -COPY --from=builder /build/dist /usr/share/nginx/html +# Copy pre-built frontend files +# These are built on the CI runner and injected via extra_files +COPY frontend/dist /usr/share/nginx/html # Create runtime config injection script RUN cat > /docker-entrypoint.d/40-inject-config.sh <<'EOF'