From 533d35f4132f3b9722e7d6fddf0a921bce0a9d52 Mon Sep 17 00:00:00 2001 From: Jintu Das Date: Fri, 20 Mar 2026 15:41:15 +0530 Subject: [PATCH] feat: optimize Docker builds and parallelize staging CI --- .github/workflows/ci.yml | 92 +++++++++++++++++++++++++++------------ docker/admin/Dockerfile | 25 +++++------ docker/handler/Dockerfile | 14 +++--- docker/poller/Dockerfile | 35 +++++++-------- 4 files changed, 100 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a709048..0eead7e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,19 +234,29 @@ jobs: aws-access-key-id: ${{ secrets.DEPLOYER_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.DEPLOYER_AWS_SECRET_ACCESS_KEY }} - # Staging deployment (mason) - triggered on staging branch - build-and-deploy-staging: + # Staging Docker builds - parallel via matrix + build-staging-images: if: github.ref == 'refs/heads/staging' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - service: admin + dockerfile: docker/admin/Dockerfile + repository: mark-admin + image_tag: mark-admin-${{ github.sha }} + - service: handler + dockerfile: docker/handler/Dockerfile + repository: mark-handler + image_tag: mark-handler-${{ github.sha }} + - service: poller + dockerfile: docker/poller/Dockerfile + repository: mark-poller + image_tag: mark-poller-${{ github.sha }} env: AWS_REGION: sa-east-1 REGISTRY: 679752396206.dkr.ecr.sa-east-1.amazonaws.com - POLLER_REPOSITORY: mark-poller - POLLER_IMAGE_TAG: mark-poller-${{ github.sha }} - HANDLER_REPOSITORY: mark-handler - HANDLER_IMAGE_TAG: mark-handler-${{ github.sha }} - ADMIN_REPOSITORY: mark-admin - ADMIN_IMAGE_TAG: mark-admin-${{ github.sha }} permissions: contents: read packages: write @@ -268,37 +278,61 @@ jobs: with: mask-password: 'true' - - name: Ensure ECR repositories exist + - name: Ensure ECR repository exists run: | - # Create repositories if they don't exist - aws ecr describe-repositories --repository-names $ADMIN_REPOSITORY --region $AWS_REGION || \ - aws ecr create-repository --repository-name $ADMIN_REPOSITORY --region $AWS_REGION --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE - aws ecr describe-repositories --repository-names $HANDLER_REPOSITORY --region $AWS_REGION || \ - aws ecr create-repository --repository-name $HANDLER_REPOSITORY --region $AWS_REGION --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE - aws ecr describe-repositories --repository-names $POLLER_REPOSITORY --region $AWS_REGION || \ - aws ecr create-repository --repository-name $POLLER_REPOSITORY --region $AWS_REGION --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE + aws ecr describe-repositories --repository-names ${{ matrix.repository }} --region $AWS_REGION || \ + aws ecr create-repository --repository-name ${{ matrix.repository }} --region $AWS_REGION --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE - - name: Build and push Admin Docker image - run: | - docker build --provenance=false --sbom=false -f docker/admin/Dockerfile -t $REGISTRY/$ADMIN_REPOSITORY:$ADMIN_IMAGE_TAG . - docker push $REGISTRY/$ADMIN_REPOSITORY:$ADMIN_IMAGE_TAG + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Build and push Invoice Handler Docker image - run: | - docker build -f docker/handler/Dockerfile -t $REGISTRY/$HANDLER_REPOSITORY:$HANDLER_IMAGE_TAG . - docker push $REGISTRY/$HANDLER_REPOSITORY:$HANDLER_IMAGE_TAG + - name: Build and push ${{ matrix.service }} Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + tags: ${{ env.REGISTRY }}/${{ matrix.repository }}:${{ matrix.image_tag }} + cache-from: type=gha,scope=staging-${{ matrix.service }} + cache-to: type=gha,scope=staging-${{ matrix.service }},mode=max + provenance: false + sbom: false + + # Staging deployment - runs after all images are built + deploy-staging: + if: github.ref == 'refs/heads/staging' + needs: build-staging-images + runs-on: ubuntu-latest + env: + AWS_REGION: sa-east-1 + REGISTRY: 679752396206.dkr.ecr.sa-east-1.amazonaws.com + POLLER_REPOSITORY: mark-poller + POLLER_IMAGE_TAG: mark-poller-${{ github.sha }} + HANDLER_REPOSITORY: mark-handler + HANDLER_IMAGE_TAG: mark-handler-${{ github.sha }} + ADMIN_REPOSITORY: mark-admin + ADMIN_IMAGE_TAG: mark-admin-${{ github.sha }} + permissions: + contents: read + packages: write - - name: Build and push Poller Docker image - run: | - docker build --provenance=false --sbom=false -f docker/poller/Dockerfile -t $REGISTRY/$POLLER_REPOSITORY:$POLLER_IMAGE_TAG . - docker push $REGISTRY/$POLLER_REPOSITORY:$POLLER_IMAGE_TAG + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: sa-east-1 + aws-access-key-id: ${{ secrets.DEPLOYER_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.DEPLOYER_AWS_SECRET_ACCESS_KEY }} # ============================================================================ # POLLER REMOVAL - TEMPORARY # ============================================================================ # Remove only the main poller Lambda function (mark_poller) before deploying the invoice # handler to prevent duplicate intent creation. Other poller Lambdas remain active. - # + # # TODO: Remove this step once poller migration is complete # ============================================================================ - name: Remove Main Poller Lambda Function diff --git a/docker/admin/Dockerfile b/docker/admin/Dockerfile index 3bea8d2c..12ff83fc 100644 --- a/docker/admin/Dockerfile +++ b/docker/admin/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 FROM public.ecr.aws/lambda/nodejs:20 AS node # ---------------------------------------- @@ -53,9 +54,10 @@ COPY yarn.lock /tmp/build/ # Install dependencies including devDependencies # Note: --mode=skip-build skips preinstall/postinstall scripts during install # This avoids the "npx only-allow pnpm" check in @eth-optimism/core-utils -# Clear yarn cache before install to avoid corrupted package downloads -# Retry install on failure to handle transient npm registry issues -RUN yarn cache clean --all && \ +# Cache mount speeds up yarn install when Docker layer cache misses +# Clear cache and retry on failure to handle corrupted downloads or transient registry issues +ENV YARN_CACHE_FOLDER=/tmp/yarn-cache +RUN --mount=type=cache,target=/tmp/yarn-cache \ yarn install --immutable --mode=skip-build || \ (yarn cache clean --all && sleep 2 && yarn install --immutable --mode=skip-build) && \ yarn workspaces foreach -A run rebuild @@ -76,18 +78,11 @@ COPY packages/adapters/database /tmp/build/packages/adapters/database COPY tsconfig.json /tmp/build/ # Build packages -# Build only the workspaces needed for admin (not all workspaces) -# Build dependencies first, then admin -RUN yarn workspace @mark/core build && \ - yarn workspace @mark/logger build && \ - yarn workspace @mark/cache build && \ - yarn workspace @mark/chainservice build && \ - yarn workspace @mark/database build && \ - yarn workspace @mark/everclear build && \ - yarn workspace @mark/prometheus build && \ - yarn workspace @mark/web3signer build && \ - yarn workspace @mark/rebalance build && \ - yarn workspace @mark/admin build +# Build packages in topological order (respects declared deps) +# Exclude root workspace (its build script would re-run foreach without our excludes) +# Exclude packages not needed by admin +RUN yarn workspaces foreach -Av --topological-dev \ + --exclude mark --exclude @mark/handler --exclude @mark/poller --exclude @mark/agent --exclude @mark/webhooks run build # ---------------------------------------- # Runtime stage diff --git a/docker/handler/Dockerfile b/docker/handler/Dockerfile index d892b5e4..5670cc98 100644 --- a/docker/handler/Dockerfile +++ b/docker/handler/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 FROM node:20 AS base # ---------------------------------------- @@ -53,7 +54,9 @@ COPY yarn.lock /tmp/build/ # Note: --mode=skip-build skips preinstall/postinstall scripts during install # This avoids the "npx only-allow pnpm" check in @eth-optimism/core-utils # Then we run rebuild to build native modules with our build tools -RUN yarn install --immutable --mode=skip-build && \ +ENV YARN_CACHE_FOLDER=/tmp/yarn-cache +RUN --mount=type=cache,target=/tmp/yarn-cache \ + yarn install --immutable --mode=skip-build && \ yarn workspaces foreach -A run rebuild # Copy source files @@ -73,10 +76,11 @@ COPY packages/adapters/database /tmp/build/packages/adapters/database COPY packages/adapters/webhooks /tmp/build/packages/adapters/webhooks COPY tsconfig.json /tmp/build/ -# Build packages -# Build core first to ensure declaration files are available -RUN yarn workspace @mark/core build && \ - yarn build +# Build packages in topological order (respects declared deps) +# Exclude root workspace (its build script would re-run foreach without our excludes) +# Exclude admin — not needed by handler +RUN yarn workspaces foreach -Av --topological-dev \ + --exclude mark --exclude @mark/admin run build # ---------------------------------------- # Runtime stage diff --git a/docker/poller/Dockerfile b/docker/poller/Dockerfile index 13ae2267..2ac91c99 100644 --- a/docker/poller/Dockerfile +++ b/docker/poller/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 FROM public.ecr.aws/lambda/nodejs:20 AS node # ---------------------------------------- @@ -53,9 +54,10 @@ COPY yarn.lock /tmp/build/ # Install dependencies including devDependencies # Note: --mode=skip-build skips preinstall/postinstall scripts during install # This avoids the "npx only-allow pnpm" check in @eth-optimism/core-utils -# Clear yarn cache before install to avoid corrupted package downloads -# Retry install on failure to handle transient npm registry issues -RUN yarn cache clean --all && \ +# Cache mount speeds up yarn install when Docker layer cache misses +# Clear cache and retry on failure to handle corrupted downloads or transient registry issues +ENV YARN_CACHE_FOLDER=/tmp/yarn-cache +RUN --mount=type=cache,target=/tmp/yarn-cache \ yarn install --immutable --mode=skip-build || \ (yarn cache clean --all && sleep 2 && yarn install --immutable --mode=skip-build) && \ yarn workspaces foreach -A run rebuild @@ -76,19 +78,11 @@ COPY packages/adapters/database /tmp/build/packages/adapters/database COPY tsconfig.json /tmp/build/ # Build packages -# Build only the workspaces needed for poller (not all workspaces) -# Build dependencies first, then poller -RUN yarn workspace @mark/core build && \ - yarn workspace @mark/logger build && \ - yarn workspace @mark/cache build && \ - yarn workspace @mark/chainservice build && \ - yarn workspace @mark/database build && \ - yarn workspace @mark/everclear build && \ - yarn workspace @mark/prometheus build && \ - yarn workspace @mark/web3signer build && \ - yarn workspace @mark/rebalance build && \ - yarn workspace @mark/agent build && \ - yarn workspace @mark/poller build +# Build packages in topological order (respects declared deps) +# Exclude root workspace (its build script would re-run foreach without our excludes) +# Exclude packages not needed by poller +RUN yarn workspaces foreach -Av --topological-dev \ + --exclude mark --exclude @mark/admin --exclude @mark/handler --exclude @mark/webhooks run build # ---------------------------------------- # Runtime stage @@ -97,7 +91,14 @@ RUN yarn workspace @mark/core build && \ FROM node AS runtime # Install dbmate for database migrations -RUN curl -fsSL -o /usr/local/bin/dbmate https://github.com/amacneil/dbmate/releases/latest/download/dbmate-linux-amd64 && \ +# Pin to specific version and verify SHA256 checksum for supply chain security +ARG DBMATE_VERSION=v2.29.3 +ARG DBMATE_SHA256=2bb1554a32d9c0bd544841d3523eae64fd60a58d7720c5d82900043dc5e87a6c +RUN set -eux; \ + curl -fsSL -o /tmp/dbmate \ + https://github.com/amacneil/dbmate/releases/download/${DBMATE_VERSION}/dbmate-linux-amd64; \ + echo "${DBMATE_SHA256} /tmp/dbmate" | sha256sum -c -; \ + mv /tmp/dbmate /usr/local/bin/dbmate; \ chmod +x /usr/local/bin/dbmate ENV NODE_ENV=production \