perf: optimize Docker build for speed — GHA cache, concurrency, fewer layers

- Switch from registry cache to type=gha (no network overhead for DinD runners)
- Add concurrency group to cancel in-progress builds on same branch
- Merge build+deploy into single job (saves job startup overhead)
- Combine 7 tool installs into single RUN layer
- Add BuildKit cache mounts for pnpm store and Next.js .next/cache
- Combine sequential RUN commands in all 3 Dockerfile stages
- Disable Next.js telemetry during build and runtime
- Expand .dockerignore to exclude tests, docs, .github
This commit is contained in:
2026-03-01 23:18:39 -08:00
parent 2a6bff793d
commit e2f456c299
3 changed files with 80 additions and 145 deletions
+14 -1
View File
@@ -11,4 +11,17 @@ README.md
.eslintcache
/test-results/
/playwright-report/
/blob-report/
/blob-report/
/mocks/
**/*.spec.ts
**/*.spec.tsx
**/*.pw.tsx
**/*.test.ts
**/*.test.tsx
/.github/
/.vscode/
/.husky/
/playwright/
CHANGELOG.md
LICENSE
.env.local
+24 -32
View File
@@ -15,18 +15,20 @@ on:
- main
workflow_dispatch:
concurrency:
group: lux-build-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: luxfi/explore
jobs:
build:
build-and-deploy:
runs-on: lux-build
permissions:
contents: read
packages: write
outputs:
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -41,9 +43,6 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Add SHORT_SHA
run: echo "SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_ENV
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
@@ -64,35 +63,28 @@ jobs:
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:amd64
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:amd64,mode=max
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
push: ${{ github.event_name != 'pull_request' }}
build-args: |
GIT_COMMIT_SHA=${{ env.SHORT_SHA }}
GIT_COMMIT_SHA=${{ github.sha }}
GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }}
deploy:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
runs-on: lux-build
needs: [build]
steps:
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
- name: Install kubectl
- name: Deploy to production
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
env:
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
run: |
mkdir -p $HOME/bin
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && mv kubectl $HOME/bin/kubectl
echo "$HOME/bin" >> $GITHUB_PATH
- name: Configure kubectl
run: doctl kubernetes cluster kubeconfig save lux-k8s
- name: Restart frontend deployments
run: |
kubectl rollout restart deployment/explorer-frontend -n lux-explorer || echo "Skipped explorer-frontend"
kubectl rollout status deployment/explorer-frontend -n lux-explorer --timeout=300s || echo "Timeout waiting for explorer-frontend"
# Install doctl + kubectl if not present
if ! command -v doctl &>/dev/null; then
curl -sL https://github.com/digitalocean/doctl/releases/latest/download/doctl-1.119.1-linux-amd64.tar.gz | tar xz -C /usr/local/bin 2>/dev/null || true
fi
if ! command -v kubectl &>/dev/null; then
curl -sLO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && mv kubectl /usr/local/bin/kubectl
fi
doctl auth init -t "$DIGITALOCEAN_ACCESS_TOKEN"
doctl kubernetes cluster kubeconfig save lux-k8s
kubectl rollout restart deployment/explorer-frontend -n lux-explorer
kubectl rollout status deployment/explorer-frontend -n lux-explorer --timeout=300s
+42 -112
View File
@@ -2,21 +2,16 @@
# *** STAGE 1: Dependencies ***
# *****************************
FROM node:22.14.0-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat python3 make g++ git
RUN ln -sf /usr/bin/python3 /usr/bin/python
RUN corepack enable && corepack prepare [email protected] --activate
# Global pnpm config for all work directories (shamefully-hoist for compat)
# Use Yarn registry as fallback for npm 403 errors from GitHub Actions
RUN echo "shamefully-hoist=true" > /root/.npmrc && \
RUN apk add --no-cache libc6-compat python3 make g++ git && \
ln -sf /usr/bin/python3 /usr/bin/python && \
corepack enable && corepack prepare [email protected] --activate && \
echo "shamefully-hoist=true" > /root/.npmrc && \
echo "strict-peer-dependencies=false" >> /root/.npmrc && \
echo "auto-install-peers=true" >> /root/.npmrc && \
echo "registry=https://registry.yarnpkg.com/" >> /root/.npmrc && \
echo "fetch-retries=5" >> /root/.npmrc
### APP
# Install dependencies
WORKDIR /app
COPY package.json pnpm-lock.yaml .npmrc ./
COPY stubs ./stubs
@@ -27,149 +22,84 @@ COPY configs/app ./configs/app
COPY toolkit/theme ./toolkit/theme
COPY toolkit/utils ./toolkit/utils
COPY toolkit/components/forms/validators/url.ts ./toolkit/components/forms/validators/url.ts
RUN pnpm install --frozen-lockfile
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
### FEATURE REPORTER
# Install dependencies
WORKDIR /feature-reporter
COPY ./deploy/tools/feature-reporter/package.json ./
RUN pnpm install --no-lockfile
### ENV VARIABLES CHECKER
# Install dependencies
WORKDIR /envs-validator
COPY ./deploy/tools/envs-validator/package.json ./
RUN pnpm install --no-lockfile
### FAVICON GENERATOR
# Install dependencies
WORKDIR /favicon-generator
COPY ./deploy/tools/favicon-generator/package.json ./
RUN pnpm install --no-lockfile
### SITEMAP GENERATOR
# Install dependencies
WORKDIR /sitemap-generator
COPY ./deploy/tools/sitemap-generator/package.json ./
RUN pnpm install --no-lockfile
### MULTICHAIN CONFIG GENERATOR
# Install dependencies
WORKDIR /multichain-config-generator
COPY ./deploy/tools/multichain-config-generator/package.json ./
RUN pnpm install --no-lockfile
### ESSENTIAL DAPPS CHAINS CONFIG GENERATOR
# Install dependencies
WORKDIR /essential-dapps-chains-config-generator
COPY ./deploy/tools/essential-dapps-chains-config-generator/package.json ./
RUN pnpm install --no-lockfile
### llms.txt GENERATOR
# Install dependencies
WORKDIR /llms-txt-generator
COPY ./deploy/tools/llms-txt-generator/package.json ./
RUN pnpm install --no-lockfile
### TOOLS (all in one layer)
COPY ./deploy/tools/feature-reporter/package.json /feature-reporter/package.json
COPY ./deploy/tools/envs-validator/package.json /envs-validator/package.json
COPY ./deploy/tools/favicon-generator/package.json /favicon-generator/package.json
COPY ./deploy/tools/sitemap-generator/package.json /sitemap-generator/package.json
COPY ./deploy/tools/multichain-config-generator/package.json /multichain-config-generator/package.json
COPY ./deploy/tools/essential-dapps-chains-config-generator/package.json /essential-dapps-chains-config-generator/package.json
COPY ./deploy/tools/llms-txt-generator/package.json /llms-txt-generator/package.json
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
cd /feature-reporter && pnpm install --no-lockfile && \
cd /envs-validator && pnpm install --no-lockfile && \
cd /favicon-generator && pnpm install --no-lockfile && \
cd /sitemap-generator && pnpm install --no-lockfile && \
cd /multichain-config-generator && pnpm install --no-lockfile && \
cd /essential-dapps-chains-config-generator && pnpm install --no-lockfile && \
cd /llms-txt-generator && pnpm install --no-lockfile
# *****************************
# ****** STAGE 2: Build *******
# *****************************
FROM node:22.14.0-alpine AS builder
RUN apk add --no-cache --upgrade libc6-compat bash jq
RUN corepack enable && corepack prepare [email protected] --activate
RUN apk add --no-cache --upgrade libc6-compat bash jq && \
corepack enable && corepack prepare [email protected] --activate
# pass build args to env variables
ARG GIT_COMMIT_SHA
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
ARG GIT_TAG
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
ARG NEXT_OPEN_TELEMETRY_ENABLED
ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED
ENV NODE_ENV production
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS="--max-old-space-size=8192"
### APP
# Copy dependencies and source code
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build SVG sprite and generate .env.registry with ENVs list and save build args into .env file
# Build SVG sprite + collect envs + build Next.js (with persistent .next/cache)
RUN set -a && \
source ./deploy/scripts/build_sprite.sh && \
./deploy/scripts/collect_envs.sh ./docs/ENVS.md && \
set +a
RUN --mount=type=cache,id=nextjs-cache,target=/app/.next/cache \
pnpm build
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
# Build app for production
ENV NODE_OPTIONS="--max-old-space-size=8192"
RUN pnpm build
### FEATURE REPORTER
# Copy dependencies and source code, then build
### TOOLS (build all in fewer layers)
COPY --from=deps /feature-reporter/node_modules ./deploy/tools/feature-reporter/node_modules
RUN cd ./deploy/tools/feature-reporter && pnpm compile_config
RUN cd ./deploy/tools/feature-reporter && pnpm build
### ENV VARIABLES CHECKER
# Copy dependencies and source code, then build
COPY --from=deps /envs-validator/node_modules ./deploy/tools/envs-validator/node_modules
RUN cd ./deploy/tools/envs-validator && pnpm build
### FAVICON GENERATOR
# Copy dependencies and source code
COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generator/node_modules
### SITEMAP GENERATOR
# Copy dependencies and source code
COPY --from=deps /sitemap-generator/node_modules ./deploy/tools/sitemap-generator/node_modules
### MULTICHAIN CONFIG GENERATOR
# Copy dependencies and source code, then build
COPY --from=deps /multichain-config-generator/node_modules ./deploy/tools/multichain-config-generator/node_modules
RUN cd ./deploy/tools/multichain-config-generator && pnpm build
### ESSENTIAL DAPPS CHAINS CONFIG GENERATOR
# Copy dependencies and source code, then build
COPY --from=deps /essential-dapps-chains-config-generator/node_modules ./deploy/tools/essential-dapps-chains-config-generator/node_modules
RUN cd ./deploy/tools/essential-dapps-chains-config-generator && pnpm build
### llms.txt GENERATOR
# Copy dependencies and source code, then build
COPY --from=deps /llms-txt-generator/node_modules ./deploy/tools/llms-txt-generator/node_modules
RUN cd ./deploy/tools/llms-txt-generator && pnpm build
RUN cd ./deploy/tools/feature-reporter && pnpm compile_config && pnpm build && \
cd /app/deploy/tools/envs-validator && pnpm build && \
cd /app/deploy/tools/multichain-config-generator && pnpm build && \
cd /app/deploy/tools/essential-dapps-chains-config-generator && pnpm build && \
cd /app/deploy/tools/llms-txt-generator && pnpm build
# *****************************
# ******* STAGE 3: Run ********
# *****************************
# Production image, copy all the files and run next
FROM node:22.14.0-alpine AS runner
RUN apk add --no-cache --upgrade bash curl jq unzip
RUN apk add --no-cache --upgrade bash curl jq unzip && \
addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
ENV NEXT_TELEMETRY_DISABLED=1
### APP
WORKDIR /app
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN mkdir .next && chown nextjs:nodejs .next
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public