From 0d9794e9686ed607d5fb3e0913a7b0530a3efb3e Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Wed, 2 Apr 2025 06:50:31 -0700 Subject: [PATCH] Add Docker arm64 builds (#14025) --- .github/workflows/build-web.yml | 236 +++++++++++++------------------- apps/web/Dockerfile | 77 ++++++++++- 2 files changed, 167 insertions(+), 146 deletions(-) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index e91fba2e87a..a0d9026e471 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -42,10 +42,11 @@ on: env: _AZ_REGISTRY: bitwardenprod.azurecr.io + jobs: setup: name: Setup - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 outputs: version: ${{ steps.version.outputs.value }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} @@ -54,7 +55,7 @@ jobs: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha }} - name: Get GitHub sha as version id: version @@ -75,133 +76,61 @@ jobs: has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT - build-artifacts: - name: Build artifacts - runs-on: ubuntu-22.04 - needs: - - setup - env: - _VERSION: ${{ needs.setup.outputs.version }} - _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - strategy: - matrix: - include: - - name: "selfhosted-open-source" - npm_command: "dist:oss:selfhost" - - name: "cloud-COMMERCIAL" - npm_command: "dist:bit:cloud" - - name: "selfhosted-COMMERCIAL" - npm_command: "dist:bit:selfhost" - - name: "cloud-QA" - npm_command: "build:bit:qa" - git_metadata: true - - name: "ee" - npm_command: "build:bit:ee" - git_metadata: true - - name: "cloud-euprd" - npm_command: "build:bit:euprd" - - name: "cloud-euqa" - npm_command: "build:bit:euqa" - git_metadata: true - - name: "cloud-usdev" - npm_command: "build:bit:usdev" - git_metadata: true - - steps: - - name: Check out repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up Node - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - with: - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - node-version: ${{ env._NODE_VERSION }} - - - name: Print environment - run: | - whoami - node --version - npm --version - docker --version - echo "GitHub ref: $GITHUB_REF" - echo "GitHub event: $GITHUB_EVENT" - - - name: Install dependencies - run: npm ci - - - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - github_token: ${{secrets.GITHUB_TOKEN}} - workflow: build-wasm-internal.yml - workflow_conclusion: success - branch: ${{ inputs.sdk_branch }} - artifacts: sdk-internal - repo: bitwarden/sdk-internal - path: ../sdk-internal - if_no_artifact_found: fail - - - name: Override SDK - if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} - working-directory: ./ - run: | - ls -l ../ - npm link ../sdk-internal - - - name: Add Git metadata to build version - working-directory: apps/web - if: matrix.git_metadata - run: | - VERSION=$( jq -r ".version" package.json) - jq --arg version "$VERSION+${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp - mv package.json.tmp package.json - - - name: Build ${{ matrix.name }} - working-directory: apps/web - run: npm run ${{ matrix.npm_command }} - - - name: Package artifact - working-directory: apps/web - run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - - - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: web-${{ env._VERSION }}-${{ matrix.name }}.zip - path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip - if-no-files-found: error - build-containers: - name: Build Docker images - runs-on: ubuntu-22.04 + name: Build artifacts and container images + runs-on: ubuntu-24.04 permissions: security-events: write id-token: write - needs: - - setup - - build-artifacts + needs: setup strategy: fail-fast: false matrix: include: - - artifact_name: cloud-QA - image_name: web-qa-cloud - - artifact_name: ee - image_name: web-ee + - artifact_name: selfhosted-open-source + image_name: web-oss + npm_command: dist:oss:selfhost + - artifact_name: cloud-COMMERCIAL + image_name: web-cloud + npm_command: dist:bit:cloud - artifact_name: selfhosted-COMMERCIAL image_name: web + npm_command: dist:bit:selfhost + - artifact_name: cloud-QA + image_name: web-qa-cloud + npm_command: build:bit:qa + git_metadata: true + - artifact_name: ee + image_name: web-ee + npm_command: build:bit:ee + git_metadata: true + - artifact_name: cloud-euprd + image_name: web-euprd + npm_command: build:bit:euprd + - artifact_name: cloud-euqa + image_name: web-euqa + npm_command: build:bit:euqa + git_metadata: true + - artifact_name: cloud-usdev + image_name: web-usdev + npm_command: build:bit:usdev + git_metadata: true env: + _NODE_VERSION: ${{ needs.setup.outputs.node_version }} _VERSION: ${{ needs.setup.outputs.version }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha }} + + - name: Check out Server repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: server + repository: bitwarden/server + ref: ${{ github.event.pull_request.head.sha && 'main' || github.ref }} - name: Check Branch to Publish env: @@ -216,6 +145,21 @@ jobs: echo "is_publish_branch=false" >> $GITHUB_ENV fi + - name: Add Git metadata to build version + working-directory: apps/web + if: matrix.git_metadata + run: | + VERSION=$( jq -r ".version" package.json) + jq --arg version "$VERSION+${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp + mv package.json.tmp package.json + + ########## Set up Docker ########## + - name: Set up QEMU emulators + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + ########## ACRs ########## - name: Login to Prod Azure if: ${{ needs.setup.outputs.has_secrets == 'true' }} @@ -225,7 +169,7 @@ jobs: - name: Log into Prod container registry if: ${{ needs.setup.outputs.has_secrets == 'true' }} - run: az acr login -n bitwardenprod + run: az acr login -n ${_AZ_REGISTRY%.azurecr.io} - name: Login to Azure - CI Subscription if: ${{ needs.setup.outputs.has_secrets == 'true' }} @@ -241,14 +185,8 @@ jobs: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" - - name: Download ${{ matrix.artifact_name }} artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip - path: apps/web - ########## Generate image tag and build Docker image ########## - - name: Generate Docker image tag + - name: Generate container image tag id: tag run: | if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then @@ -270,10 +208,6 @@ jobs: echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT ########## Build Image ########## - - name: Extract artifact - working-directory: apps/web - run: unzip web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip - - name: Generate image full name id: image-name env: @@ -283,16 +217,38 @@ jobs: - name: Build Docker image if: ${{ needs.setup.outputs.has_secrets == 'true' }} - id: build-docker + id: build-container uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: - context: apps/web + build-args: | + NODE_VERSION=${{ env._NODE_VERSION }} + NPM_COMMAND=${{ matrix.npm_command }} + context: . file: apps/web/Dockerfile - platforms: linux/amd64 + platforms: | + linux/amd64, + linux/arm/v7, + linux/arm64 push: true tags: ${{ steps.image-name.outputs.name }} - secrets: | - "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + + - name: Zip project + working-directory: apps/web + env: + IMAGE_NAME: ${{ steps.image-name.outputs.name }} + run: | + mkdir build + docker run --rm --volume $(pwd)/build:/temp --entrypoint bash \ + $IMAGE_NAME -c "cp -r ./ /temp" + + zip -r web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip build + + - name: Upload ${{ matrix.artifact_name }} artifact + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip + path: apps/web/web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip + if-no-files-found: error - name: Install Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' @@ -301,7 +257,7 @@ jobs: - name: Sign image with Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' env: - DIGEST: ${{ steps.build-docker.outputs.digest }} + DIGEST: ${{ steps.build-container.outputs.digest }} TAGS: ${{ steps.image-name.outputs.name }} run: | IFS="," read -a tags <<< "${TAGS}" @@ -329,19 +285,19 @@ jobs: ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} - name: Log out of Docker - run: docker logout + run: docker logout $_AZ_REGISTRY + crowdin-push: name: Crowdin Push if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' - needs: - - build-artifacts - runs-on: ubuntu-22.04 + needs: build-containers + runs-on: ubuntu-24.04 steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -367,12 +323,12 @@ jobs: upload_sources: true upload_translations: false + trigger-web-vault-deploy: name: Trigger web vault deploy if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' - runs-on: ubuntu-22.04 - needs: - - build-artifacts + runs-on: ubuntu-24.04 + needs: build-containers steps: - name: Login to Azure - CI Subscription uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -402,13 +358,13 @@ jobs: } }) + check-failures: name: Check for failures if: always() - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: - setup - - build-artifacts - build-containers - crowdin-push - trigger-web-vault-deploy diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 09dac7f4c48..05def421c82 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,18 +1,83 @@ -FROM ghcr.io/bitwarden/server +############################################### +# Build stage 1 # +############################################### +ARG NODE_VERSION=20 +FROM --platform=$BUILDPLATFORM node:${NODE_VERSION} AS node-build +ARG NPM_COMMAND=dist:bit:selfhost + +WORKDIR /source +COPY . . + +RUN npm ci + +WORKDIR /source/apps/web +RUN npm run ${NPM_COMMAND} + +############################################### +# Build stage 2 # +############################################### +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +# Determine proper runtime value for .NET +# We put the value in a file to be read by later layers. +RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + RID=linux-x64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + RID=linux-arm64 ; \ + elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ + RID=linux-arm ; \ + fi \ + && echo "RID=$RID" > /tmp/rid.txt + +# Copy csproj files as distinct layers +WORKDIR /source +COPY server/util/Server/*.csproj ./util/Server/ +COPY server/Directory.Build.props . +COPY server/.editorconfig . + +# Restore Server project dependencies and tools +WORKDIR /source/util/Server +RUN . /tmp/rid.txt && dotnet restore -r $RID + +# Copy required project files +WORKDIR /source +COPY server/util/Server/. ./util/Server/ +COPY server/.git/. ./.git/ + +# Build Server app +WORKDIR /source/util/Server +RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Server --no-restore --no-self-contained -r $RID + +WORKDIR /app + +############################################### +# App stage # +############################################### +FROM mcr.microsoft.com/dotnet/aspnet:8.0 + +ARG TARGETPLATFORM LABEL com.bitwarden.product="bitwarden" +ENV ASPNETCORE_ENVIRONMENT=Production +ENV ASPNETCORE_URLS=http://+:5000 +EXPOSE 5000 RUN apt-get update \ && apt-get install -y --no-install-recommends \ gosu \ curl \ -&& rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* + +# Copy app from the build stage +WORKDIR /bitwarden_server +COPY --from=build /app/Server ./ -ENV ASPNETCORE_URLS http://+:5000 WORKDIR /app -EXPOSE 5000 -COPY ./build . -COPY entrypoint.sh / +COPY --from=node-build /source/apps/web/build . +COPY --from=node-build /source/apps/web/entrypoint.sh / RUN chmod +x /entrypoint.sh HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1