diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000..5005092c7e
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,661 @@
+# .circleci/config.yml
+version: 2.1
+
+executors:
+  ubuntu2204arm64:
+    machine:
+      image: ubuntu-2204:current
+    resource_class: arm-medium
+  ubuntu2204amd64:
+    machine:
+      image: ubuntu-2204:current
+    resource_class: medium
+  ubuntu2204arm64large:
+    machine:
+      image: ubuntu-2204:current
+    resource_class: arm-large
+
+jobs:
+  build-multi-arch:
+    parameters:
+      platforms:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - run:
+          name: "Prepare workflow environment variables"
+          command: |
+            echo 'export BRANCH="${CIRCLE_BRANCH//\//-}"' >> $BASH_ENV
+            cat $BASH_ENV
+            source $BASH_ENV
+            echo "Workflow environment variables:"
+            echo $BRANCH
+      - run: uname -a
+      - run: docker info
+      - checkout
+      - run:
+          name: "Build Docker images"
+          command: |
+            echo "Branch is $BRANCH"
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            NAME=${NAMESPACE} VERSION=${BRANCH}_${ARCH} BUILD_DATE=${BUILD_DATE} BUILD_ARGS=${BUILD_ARGS} make all_${ARCH}
+      - run:
+          name: "Inspect Docker Images"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            export TAG=${VERSION}_${ARCH}-${BUILD_DATE}
+            echo "TAG is ${TAG}"
+            docker image inspect $NAMESPACE/base:$TAG
+            docker image inspect $NAMESPACE/node-base:$TAG
+            docker image inspect $NAMESPACE/hub:$TAG
+            docker image inspect $NAMESPACE/node-chromium:$TAG
+            docker image inspect $NAMESPACE/standalone-chromium:$TAG
+            docker image inspect $NAMESPACE/node-firefox:$TAG
+            docker image inspect $NAMESPACE/standalone-firefox:$TAG
+            if [ "${ARCH}" = "amd64" ]; then
+              docker image inspect $NAMESPACE/node-chrome:$TAG
+              docker image inspect $NAMESPACE/standalone-chrome:$TAG
+              docker image inspect $NAMESPACE/node-edge:$TAG
+              docker image inspect $NAMESPACE/standalone-edge:$TAG
+            fi
+      - run:
+          name: "Save Docker Images in Cache"
+          command: |  
+            export VERSION=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            export TAG=${VERSION}_${ARCH}-${BUILD_DATE}
+            mkdir images
+            echo "TAG is ${TAG}"
+            echo $NAMESPACE/base:$TAG
+            echo $NAMESPACE/node-base:$TAG
+            echo $NAMESPACE/hub:$TAG
+            echo $NAMESPACE/node-chromium:$TAG
+            echo $NAMESPACE/standalone-chromium:$TAG
+            echo $NAMESPACE/node-firefox:$TAG
+            echo $NAMESPACE/standalone-firefox:$TAG
+            if [ "${ARCH}" = "amd64" ]; then
+              echo $NAMESPACE/node-chrome:$TAG
+              echo $NAMESPACE/standalone-chrome:$TAG
+              echo $NAMESPACE/node-edge:$TAG
+              echo $NAMESPACE/standalone-edge:$TAG
+            fi
+            docker save -o images/${ARCH}.tar \
+              $NAMESPACE/base:$TAG\
+              $NAMESPACE/node-base:$TAG\
+              $NAMESPACE/hub:$TAG\
+              $NAMESPACE/node-chromium:$TAG\
+              $NAMESPACE/standalone-chromium:$TAG\
+              $NAMESPACE/node-firefox:$TAG\
+              $NAMESPACE/standalone-firefox:$TAG\
+              $(if [ "${ARCH}" = "amd64" ]; then echo "$NAMESPACE/node-chrome:$TAG $NAMESPACE/standalone-chrome:$TAG"; fi) \
+              $(if [ "${ARCH}" = "amd64" ]; then echo "$NAMESPACE/node-edge:$TAG $NAMESPACE/standalone-edge:$TAG"; fi)
+      - save_cache:
+          key: multi-arch-images-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-<< parameters.platforms >>
+          paths:
+            - images
+      - store_artifacts:
+          path: images
+
+  test-multi-arch:
+    parameters:
+      platforms:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - checkout
+      - restore_cache:
+          keys:
+            - multi-arch-images-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-<< parameters.platforms >>
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Load built images from cache into Docker"
+          command: |
+            echo "CIRCLE_WORKFLOW_ID = " $CIRCLE_WORKFLOW_ID
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            docker load -i images/${ARCH}.tar
+      - run:
+          name: "Use Python3 and pip3 instead of Python2.7"
+          command: |
+            echo "Use Python3 and pip3 instead of Python2.7"
+            sed -i 's/pip /pip3 /g' tests/bootstrap.sh
+            sed -i 's/python /python3 /g' tests/bootstrap.sh
+            sed -i 's/-m pip3 /-m pip /g' tests/bootstrap.sh
+      - run:
+          name: "Test Docker images"
+          no_output_timeout: 2m
+          command: |
+            export USE_RANDOM_USER=false
+            export BRANCH=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            USE_RANDOM_USER_ID=${USE_RANDOM_USER} NAMESPACE=${NAMESPACE} VERSION=${BRANCH}_${ARCH} BUILD_DATE=${BUILD_DATE} SKIP_BUILD=true make test_${ARCH}
+
+  test-video:
+    parameters:
+      platforms:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - checkout
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Use Python3 and pip3 instead of Python2.7"
+          command: |
+            echo "Use Python3 and pip3 instead of Python2.7"
+            sed -i 's/pip /pip3 /g' tests/bootstrap.sh
+            sed -i 's/python /python3 /g' tests/bootstrap.sh
+            sed -i 's/-m pip3 /-m pip /g' tests/bootstrap.sh
+      - run:
+          name: "Test and Build Docker images"
+          no_output_timeout: 2m
+          command: |
+            export USE_RANDOM_USER=false
+            export BRANCH=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            NAME=${NAMESPACE} USE_RANDOM_USER_ID=${USE_RANDOM_USER} NAMESPACE=${NAMESPACE} FFMPEG_TAG_VERSION=${BRANCH}_${ARCH} VERSION=${BRANCH}_${ARCH} BUILD_DATE=${BUILD_DATE} make test_video
+      - store_artifacts:
+          path: tests/videos
+      - run:
+          name: "Inspect Docker Images"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            export TAG=${VERSION}_${ARCH}-${BUILD_DATE}
+            echo "TAG is ${TAG}"
+            docker image inspect $NAMESPACE/video:$TAG
+      - run:
+          name: "Save Docker Image in Cache"
+          command: |
+            export VERSION=${CIRCLE_BRANCH//\//-}
+            ARCH=$(echo << parameters.platforms >> | sed 's/linux\///')
+            export TAG=${VERSION}_${ARCH}-${BUILD_DATE}
+            mkdir images
+            echo "TAG is ${TAG}"
+            echo $NAMESPACE/video:$TAG
+            docker save -o images/video.tar \
+              $NAMESPACE/video:$TAG
+      - save_cache:
+          key: video-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-<< parameters.platforms >>
+          paths:
+            - images
+      - store_artifacts:
+          path: images
+
+  manifest-multi-arch:
+    parameters:
+      image-name:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - checkout
+      - restore_cache:
+          keys:
+            - multi-arch-images-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-linux/arm64
+      - restore_cache:
+          keys:
+            - multi-arch-images-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-linux/amd64
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Load built images from cache into Docker"
+          command: |
+            echo "CIRCLE_WORKFLOW_ID = " $CIRCLE_WORKFLOW_ID
+            docker load -i images/arm64.tar
+            docker load -i images/amd64.tar
+      - run:
+          name: "Create manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            ARM_TAG=${VERSION}_arm64-${BUILD_DATE}
+            AMD_TAG=${VERSION}_amd64-${BUILD_DATE}
+            echo "Creating ${IMAGE_NAME}:${MANIFEST_TAG} based on ${IMAGE_NAME}:${ARM_TAG} and ${IMAGE_NAME}:${AMD_TAG}"
+            MANIFEST_IMAGE=${IMAGE_NAME}:${MANIFEST_TAG}
+            ARM_IMAGE=${IMAGE_NAME}_arm64:${MANIFEST_TAG}
+            AMD_IMAGE=${IMAGE_NAME}_amd64:${MANIFEST_TAG}
+            docker tag ${IMAGE_NAME}:${ARM_TAG} ${ARM_IMAGE}
+            docker tag ${IMAGE_NAME}:${AMD_TAG} ${AMD_IMAGE}
+            echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
+            docker push ${ARM_IMAGE}
+            docker push ${AMD_IMAGE}
+            docker manifest create ${MANIFEST_IMAGE} ${ARM_IMAGE} ${AMD_IMAGE}
+            docker manifest push ${MANIFEST_IMAGE}
+      - run:
+          name: "Inspect manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            docker manifest inspect ${IMAGE_NAME}:${MANIFEST_TAG}
+
+  manifest-amd-only-arch:
+    parameters:
+      image-name:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - checkout
+      - restore_cache:
+          keys:
+            - multi-arch-images-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-linux/amd64
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Load built images from cache into Docker"
+          command: |
+            echo "CIRCLE_WORKFLOW_ID = " $CIRCLE_WORKFLOW_ID
+            docker load -i images/amd64.tar
+      - run:
+          name: "Create manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            AMD_TAG=${VERSION}_amd64-${BUILD_DATE}
+            echo "Creating ${IMAGE_NAME}:${MANIFEST_TAG} based on ${IMAGE_NAME}:${AMD_TAG}"
+            MANIFEST_IMAGE=${IMAGE_NAME}:${MANIFEST_TAG}
+            AMD_IMAGE=${IMAGE_NAME}_amd64:${MANIFEST_TAG}
+            docker tag ${IMAGE_NAME}:${AMD_TAG} ${AMD_IMAGE}
+            echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
+            docker push ${AMD_IMAGE}
+            docker manifest create ${MANIFEST_IMAGE} ${AMD_IMAGE}
+            docker manifest push ${MANIFEST_IMAGE}
+      - run:
+          name: "Inspect manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            docker manifest inspect ${IMAGE_NAME}:${MANIFEST_TAG}
+
+  manifest-amd-image:
+    parameters:
+      image-name:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      BUILD_DATE: today
+    steps:
+      - checkout
+      - restore_cache:
+          keys:
+            - << parameters.image-name >>-{{ .Branch }}-{{ .Environment.CIRCLE_WORKFLOW_ID }}-linux/amd64
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Load built images from cache into Docker"
+          command: |
+            echo "CIRCLE_WORKFLOW_ID = " $CIRCLE_WORKFLOW_ID
+            docker load -i images/<< parameters.image-name >>.tar
+      - run:
+          name: "Create manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            AMD_TAG=${VERSION}_amd64-${BUILD_DATE}
+            echo "Creating ${IMAGE_NAME}:${MANIFEST_TAG} based on ${IMAGE_NAME}:${AMD_TAG}"
+            MANIFEST_IMAGE=${IMAGE_NAME}:${MANIFEST_TAG}
+            AMD_IMAGE=${IMAGE_NAME}_amd64:${MANIFEST_TAG}
+            docker tag ${IMAGE_NAME}:${AMD_TAG} ${AMD_IMAGE}
+            echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
+            docker push ${AMD_IMAGE}
+            docker manifest create ${MANIFEST_IMAGE} ${AMD_IMAGE}
+            docker manifest push ${MANIFEST_IMAGE}
+      - run:
+          name: "Inspect manifest"
+          command: |
+            VERSION=${CIRCLE_BRANCH//\//-}
+            IMAGE_NAME=$NAMESPACE/<< parameters.image-name >>
+            MANIFEST_TAG=${VERSION}-${BUILD_DATE}
+            docker manifest inspect ${IMAGE_NAME}:${MANIFEST_TAG}
+
+  deploy-multi-arch:
+    parameters:
+      platforms:
+        type: string
+      build-args:
+        type: string
+      machine-type:
+        type: executor
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      PLATFORMS: << parameters.platforms >>
+      BUILD_ARGS: << parameters.build-args >>
+      DEPLOY_BRANCH: trunk
+      GITHUB_USER: seleniumhq-community
+      GITHUB_REPO: docker-seleniarm
+    steps:
+      - checkout
+      - run:
+          name: "Prepare workflow environment variables"
+          command: |
+            export SELENIUM_VERSION=$(grep selenium-server Base/Dockerfile | sed 's/.*-\([^-]*\)\.jar \\/\1/' | head -n 1)
+            echo "Prepare workflow environment variables"
+            echo 'export BRANCH='$SELENIUM_VERSION >> $BASH_ENV
+            echo 'export BUILD_DATE=$(date '+%Y%m%d')' >> $BASH_ENV
+            echo 'export RELEASE_TAG="seleniarm-v`echo $BRANCH`-`echo $BUILD_DATE`"' >> $BASH_ENV
+            source $BASH_ENV
+            echo "Workflow environment variables:"
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            echo NAMESPACE="$NAMESPACE"
+            echo PLATFORMS="$PLATFORMS"
+            echo BUILD_ARGS="$BUILD_ARGS"
+            echo DEPLOY_BRANCH="$DEPLOY_BRANCH"
+            echo GITHUB_USER="$GITHUB_USER"
+            echo GITHUB_REPO="$GITHUB_REPO"
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Check if branch is deployable (contains [deploy] in commit msg on $DEPLOY_BRANCH"
+          command: |
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            export CI_DEPLOY=`git log --format=oneline -n 1 | grep '\[deploy\]'` && echo "$CI_DEPLOY"
+            if [ -z "$CI_DEPLOY" ] || [ "$CIRCLE_BRANCH" != "$DEPLOY_BRANCH" ]; then
+              echo "Cancelling run. Pass [deploy] in commit message on $DEPLOY_BRANCH to deploy"
+              circleci-agent step halt
+            else 
+              echo "[deploy] is present in commit message to $DEPLOY_BRANCH. Running workflow to deploy container images"
+            fi
+      - run:
+          name: "Build and Push Docker images"
+          command: |
+            echo "Login to Docker, and setup to use a buildx builder and push built multi-arch images"
+            docker buildx use `docker buildx create`
+            docker buildx ls
+            docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            export CI_DEPLOY=`git log --format=oneline -n 1 | grep '\[deploy\]'` && echo "$CI_DEPLOY"
+            NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} PLATFORMS=${PLATFORMS} BUILD_ARGS=${BUILD_ARGS} make build_multi
+      - run:
+          name: "Tag browser images and update latest tag"
+          command: |
+            NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} PUSH_IMAGE=true make tag_and_push_multi_arch_browser_images
+            NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} make tag_multi_arch_latest
+      - run:
+          name: "Generate release notes"
+          command: |
+            export LATEST_TAG=$(git describe --tags --abbrev=0)
+            sh generate_multi-arch-release_notes.sh $LATEST_TAG $CIRCLE_BRANCH $BRANCH $BUILD_DATE
+            sudo apt-get update -y && sudo apt-get install python3-venv
+            go install github.com/github-release/github-release@v0.10.0
+            cat release_notes.md
+            export GITHUB_TOKEN=$(sh get-access-token.sh | tail -n 1)
+            github-release release --tag $RELEASE_TAG --name $RELEASE_TAG --description "`cat release_notes.md`"
+
+  deploy-multi-arch-full-grid:
+    parameters:
+      platforms:
+        type: string
+      build-args:
+        type: string
+      machine-type:
+        type: executor
+      circle-job:
+        type: env_var_name
+        default: CIRCLE_JOB
+      make-targets:
+        type: string
+    executor: << parameters.machine-type >>
+    environment:
+      NAMESPACE: seleniarm
+      PLATFORMS: << parameters.platforms >>
+      BUILD_ARGS: << parameters.build-args >>
+      MAKE_TARGETS: << parameters.make-targets >>
+      DEPLOY_BRANCH: trunk
+      GITHUB_USER: seleniumhq-community
+      GITHUB_REPO: docker-seleniarm
+    steps:
+      - checkout
+      - run:
+          name: Debug
+          command: |
+            echo $CIRCLE_JOB
+            echo ${<< parameters.circle-job >>}
+      - when:
+          condition:
+            equal: [ base_multi, << parameters.make-targets >> ]
+          steps:
+            - run: echo "CIRCLE_JOB name is deploy-multi-arch-base"
+      - when:
+          condition:
+            equal: [ tag_and_push_multi_arch_browser_images, << parameters.make-targets >> ]
+          steps:
+            - run: echo "CIRCLE_JOB name is release notes, so we proceed"
+      - run:
+          name: "Prepare workflow environment variables"
+          command: |
+            export SELENIUM_VERSION=$(grep selenium-server Base/Dockerfile | sed 's/.*-\([^-]*\)\.jar \\/\1/' | head -n 1)
+            echo "Prepare workflow environment variables"
+            echo 'export BRANCH='$SELENIUM_VERSION >> $BASH_ENV
+            echo 'export BUILD_DATE=$(date '+%Y%m%d')' >> $BASH_ENV
+            #echo 'export BUILD_DATE=20230110' >> $BASH_ENV
+            echo 'export RELEASE_TAG="seleniarm-v`echo $BRANCH`-`echo $BUILD_DATE`"' >> $BASH_ENV
+            source $BASH_ENV
+            echo "Workflow environment variables:"
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            echo NAMESPACE="$NAMESPACE"
+            echo PLATFORMS="$PLATFORMS"
+            echo BUILD_ARGS="$BUILD_ARGS"
+            echo DEPLOY_BRANCH="$DEPLOY_BRANCH"
+            echo GITHUB_USER="$GITHUB_USER"
+            echo GITHUB_REPO="$GITHUB_REPO"
+      - run: uname -a
+      - run: docker info
+      - run:
+          name: "Check if branch is deployable (contains [deploy] in commit msg on $DEPLOY_BRANCH"
+          command: |
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            export CI_DEPLOY=`git log --format=oneline -n 1 | grep '\[deploy\]'` && echo "$CI_DEPLOY"
+            if [ -z "$CI_DEPLOY" ] || [ "$CIRCLE_BRANCH" != "$DEPLOY_BRANCH" ]; then
+              echo "Cancelling run. Pass [deploy] in commit message on $DEPLOY_BRANCH to deploy"
+              circleci-agent step halt
+            else 
+              echo "[deploy] is present in commit message to $DEPLOY_BRANCH. Running workflow to deploy container images"
+            fi
+      - run:
+          name: "Build and Push Docker images"
+          command: |
+            echo "Login to Docker, and setup to use a buildx builder and push built multi-arch images"
+            docker buildx use `docker buildx create`
+            docker buildx ls
+            # For release notes, we'll pull images anonymously without login.
+            # if [ "$MAKE_TARGETS" != "tag_and_push_multi_arch_browser_images" ]; then
+            docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
+            # fi
+            echo BRANCH="$BRANCH"
+            echo BUILD_DATE="$BUILD_DATE"
+            echo RELEASE_TAG="$RELEASE_TAG"
+            export CI_DEPLOY=`git log --format=oneline -n 1 | grep '\[deploy\]'` && echo "$CI_DEPLOY"
+            #NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} PLATFORMS=${PLATFORMS} BUILD_ARGS=${BUILD_ARGS} sh build-and-push.sh $MAKE_TARGETS
+            
+            export NAME=${NAMESPACE}
+            export VERSION=${BRANCH}
+            export BUILD_DATE=${BUILD_DATE}
+            export PLATFORMS=${PLATFORMS}
+            export BUILD_ARGS=${BUILD_ARGS}
+            # If there are build errors, let's retry
+            max=5; until sh build-and-push.sh $MAKE_TARGETS; do if [ $((--max)) = 0 ]; then echo Giving up; break; fi; done
+
+      - when:
+          condition:
+            equal: [ tag_and_push_multi_arch_browser_images, << parameters.make-targets >> ]
+          steps:
+            - run:
+                name: "Push major/minor and browser tags to Docker Hub and update latest tag"
+                command: |
+                  NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} PUSH_IMAGE=true make tag_and_push_multi_arch_browser_images
+                  NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} make tag_multi_arch_latest
+            - run:
+                name: "Push major/minor tags to Docker Hub for non-browser images"
+                command: |
+                  NAME=${NAMESPACE} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} PUSH_IMAGE=true make tag_major_minor_multi_arch
+            - run:
+                name: "Generate release notes"
+                command: |
+                  docker logout
+                  export LATEST_TAG=$(git describe --tags --abbrev=0)
+                  sh generate_multi-arch-release_notes.sh $LATEST_TAG $CIRCLE_BRANCH $BRANCH $BUILD_DATE
+                  sudo apt-get update -y && sudo apt-get install python3-venv
+                  go install github.com/github-release/github-release@v0.10.0
+                  cat release_notes.md
+                  export GITHUB_TOKEN=$(sh get-access-token.sh | tail -n 1)
+                  github-release release --tag $RELEASE_TAG --name $RELEASE_TAG --description "`cat release_notes.md`"
+
+workflows:
+  build-and-test-multi-arch:
+    jobs:
+      - build-multi-arch:
+          name: build-multi-arch-arm64
+          platforms: linux/arm64
+          machine-type: ubuntu2204arm64
+      - build-multi-arch:
+          name: build-multi-arch-amd64
+          platforms: linux/amd64
+          machine-type: ubuntu2204amd64
+      - test-multi-arch:
+          name: test-multi-arch-arm64
+          requires: [build-multi-arch-arm64]
+          platforms: linux/arm64
+          machine-type: ubuntu2204arm64
+      - test-multi-arch:
+          name: test-multi-arch-amd64
+          requires: [build-multi-arch-amd64]
+          platforms: linux/amd64
+          machine-type: ubuntu2204amd64
+      - manifest-multi-arch:
+          name: manifest-multi-arch-<< matrix.image-name >>
+          requires: [test-multi-arch-arm64, test-multi-arch-amd64]
+          machine-type: ubuntu2204amd64
+          matrix:
+            parameters:
+              image-name: [ base, node-base, hub, node-chromium, standalone-chromium, node-firefox, standalone-firefox ]
+      - manifest-amd-only-arch:
+          name: manifest-amd-arch-<< matrix.image-name >>
+          requires: [test-multi-arch-amd64]
+          machine-type: ubuntu2204amd64
+          matrix:
+            parameters:
+              image-name: [ node-chrome, standalone-chrome, node-edge, standalone-edge ]
+
+  build-and-test-video:
+    jobs:
+      -  test-video:
+           name: test-video-amd64
+           platforms: linux/amd64
+           machine-type: ubuntu2204amd64
+      - manifest-amd-image:
+          name: manifest-video-amd64
+          requires: [test-video-amd64]
+          machine-type: ubuntu2204amd64
+          image-name: video
+
+  deploy-multi-arch-full-grid:
+    jobs:
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-base
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: base_multi
+          machine-type: ubuntu2204arm64large
+          filters:
+            branches:
+              only:
+                - trunk
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-full-grid
+          requires: [deploy-multi-arch-base]
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: grid_multi
+          machine-type: ubuntu2204arm64
+          filters:
+            branches:
+              only:
+                - trunk
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-node-base
+          requires: [deploy-multi-arch-base]
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: node_base_multi
+          machine-type: ubuntu2204arm64large
+          filters:
+            branches:
+              only:
+                - trunk
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-firefox
+          requires: [deploy-multi-arch-node-base]
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: firefox_multi
+          machine-type: ubuntu2204arm64large
+          filters:
+            branches:
+              only:
+                - trunk
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-chromium
+          requires: [deploy-multi-arch-node-base]
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: chromium_multi
+          machine-type: ubuntu2204arm64large
+          filters:
+            branches:
+              only:
+                - trunk
+      - deploy-multi-arch-full-grid:
+          name: deploy-multi-arch-release-notes
+          requires: [deploy-multi-arch-firefox,deploy-multi-arch-chromium]
+          platforms: linux/arm64,linux/amd64,linux/arm/v7
+          build-args: --push
+          make-targets: tag_and_push_multi_arch_browser_images
+          machine-type: ubuntu2204arm64
+          filters:
+            branches:
+              only:
+                - trunk
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 637f89b292..5aec98bec2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,6 +6,12 @@ updates:
     interval: daily
     time: '08:00'
   open-pull-requests-limit: 99
+- package-ecosystem: docker
+  directory: "/StandaloneChromium"
+  schedule:
+    interval: daily
+    time: '08:00'
+  open-pull-requests-limit: 99
 - package-ecosystem: docker
   directory: "/StandaloneFirefox"
   schedule:
@@ -36,6 +42,12 @@ updates:
     interval: daily
     time: '08:00'
   open-pull-requests-limit: 99
+- package-ecosystem: docker
+  directory: "/NodeChromium"
+  schedule:
+    interval: daily
+    time: '08:00'
+  open-pull-requests-limit: 99
 - package-ecosystem: docker
   directory: "/"
   schedule:
diff --git a/.github/workflows/build-test-multi-arch.yml b/.github/workflows/build-test-multi-arch.yml
new file mode 100644
index 0000000000..b8e9c941d5
--- /dev/null
+++ b/.github/workflows/build-test-multi-arch.yml
@@ -0,0 +1,59 @@
+name: Build & test multi-arch
+
+on:
+  # push:
+  #   branches:
+  #     - multi-arch-tests
+  #     - qemu-user-static
+  # pull_request:
+  #   branches:
+  #     - trunk
+  workflow_dispatch:
+
+jobs:
+  build-and-test-multi-arch:
+    # Skip job based on the commit message, only works in push to branches for now
+    if: contains(toJson(github.event.commits), '[skip ci]') == false
+    name: Build & test multi-arch
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        use-random-user: [false, true]
+        arch: [amd64, arm64]
+      fail-fast: false
+      
+    steps:
+      - uses: actions/checkout@v1
+      - name: Output Docker info
+        run: docker info
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.8
+      - name: Get branch name (only for push to branch)
+        if: github.event_name == 'push'
+        run: echo "BRANCH=$(echo ${PUSH_BRANCH##*/})" >> $GITHUB_ENV
+        env:
+          PUSH_BRANCH: ${{ github.ref }}
+      - name: Get target branch name (only for PRs)
+        if: github.event_name == 'pull_request'
+        run: echo "BRANCH=$(echo ${TARGET_BRANCH##*/})" >> $GITHUB_ENV
+        env:
+          TARGET_BRANCH: ${{ github.head_ref }}
+      - name: Output branch name
+        run: echo ${BRANCH}
+      - name: Sets build date
+        run: echo "BUILD_DATE=$(date '+%Y%m%d')" >> $GITHUB_ENV
+      - name: Build Docker images
+        run: VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} ARCH=${ARCH} make build_multi
+        if: matrix.arch == 'arm64' && matrix.use-random-user == 'false'
+      - name: Test Docker images
+        run: |
+          docker run -d --platform linux/${ARCH} --rm -it -p 4444:4444 -p 7900:7900 --shm-size 2g selenium/standalone-firefox:${BRANCH}-${BUILD_DATE}
+          USE_RANDOM_USER_ID=${USE_RANDOM_USER} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} ARCH=${ARCH} SKIP_BUILD=true make test_firefox_standalone_multi
+          #USE_RANDOM_USER_ID=${USE_RANDOM_USER} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} ARCH=${ARCH} SKIP_BUILD=true make test_chromium_standalone_multi
+        env:
+          USE_RANDOM_USER: ${{ matrix.use-random-user }}
+          ARCH: ${{ matrix.arch }}
+        if: matrix.arch == 'arm64' && matrix.use-random-user == 'false'
+
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index b40db3af6b..63b94252cd 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -2,7 +2,11 @@ name: Build & test
 
 on:
   push:
+    paths-ignore:
+      - '.circleci/**'
   pull_request:
+    paths-ignore:
+      - '.circleci/**'
 
 permissions:
   contents: read
diff --git a/.github/workflows/helm-chart-test.yml b/.github/workflows/helm-chart-test.yml
index 887e6b5daa..6bd18a4ec5 100644
--- a/.github/workflows/helm-chart-test.yml
+++ b/.github/workflows/helm-chart-test.yml
@@ -2,7 +2,11 @@ name: Lint and Test Helm Charts
 
 on:
   push:
+    paths-ignore:
+      - '.circleci/**'
   pull_request:
+    paths-ignore:
+      - '.circleci/**'
   workflow_dispatch:
 
 permissions:
diff --git a/.github/workflows/test-video.yml b/.github/workflows/test-video.yml
index 02aa7f7c46..4982447f5e 100644
--- a/.github/workflows/test-video.yml
+++ b/.github/workflows/test-video.yml
@@ -2,7 +2,11 @@ name: Test video files
 
 on:
   push:
+    paths-ignore:
+      - '.circleci/**'
   pull_request:
+    paths-ignore:
+      - '.circleci/**'
 
 permissions:
   contents: read
@@ -41,6 +45,11 @@ jobs:
         with:
           name: chrome_video
           path: ./tests/videos/chrome_video.mp4
+      - name: Upload recorded Chromium video
+        uses: actions/upload-artifact@v4
+        with:
+          name: chromium_video
+          path: ./tests/videos/chromium_video.mp4
       - name: Upload recorded Edge video
         uses: actions/upload-artifact@v4
         with:
diff --git a/Base/Dockerfile b/Base/Dockerfile
index ec37f81107..a5189f6755 100644
--- a/Base/Dockerfile
+++ b/Base/Dockerfile
@@ -18,9 +18,12 @@ USER root
 #================================================
 # Customize sources for apt-get
 #================================================
-RUN  echo "deb http://archive.ubuntu.com/ubuntu jammy main universe\n" > /etc/apt/sources.list \
-  && echo "deb http://archive.ubuntu.com/ubuntu jammy-updates main universe\n" >> /etc/apt/sources.list \
-  && echo "deb http://security.ubuntu.com/ubuntu jammy-security main universe\n" >> /etc/apt/sources.list
+RUN  echo "deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main universe\n" > /etc/apt/sources.list \
+  && echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main universe\n" >> /etc/apt/sources.list \
+  && echo "deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main universe\n" >> /etc/apt/sources.list \
+  && echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main universe\n" >> /etc/apt/sources.list \
+  && echo "deb [arch=amd64,i386] http://security.ubuntu.com/ubuntu jammy-security main universe\n" >> /etc/apt/sources.list \
+  && echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main universe\n" >> /etc/apt/sources.list
 
 # No interactive frontend during docker build
 ENV DEBIAN_FRONTEND=noninteractive \
@@ -47,7 +50,8 @@ RUN apt-get -qqy update \
     gnupg2 \
     libnss3-tools \
   && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \
-  && sed -i 's/securerandom\.source=file:\/dev\/random/securerandom\.source=file:\/dev\/urandom/' ./usr/lib/jvm/java-11-openjdk-amd64/conf/security/java.security
+  && ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
+  && sed -i 's/securerandom\.source=file:\/dev\/random/securerandom\.source=file:\/dev\/urandom/' ./usr/lib/jvm/java-11-openjdk-${ARCH}/conf/security/java.security
 
 #===================
 # Timezone settings
diff --git a/Makefile b/Makefile
index 5df2eb27ec..01f320233e 100644
--- a/Makefile
+++ b/Makefile
@@ -26,13 +26,29 @@ all: hub \
 	chrome \
 	edge \
 	firefox \
+	chromium \
 	docker \
 	standalone_chrome \
 	standalone_edge \
 	standalone_firefox \
+	standalone_chromium \
 	standalone_docker \
 	video
 
+all_amd64: all
+
+all_arm64: hub \
+	distributor \
+	router \
+	sessions \
+	sessionqueue \
+	event_bus \
+	firefox \
+	chromium \
+	docker \
+	standalone_firefox \
+	standalone_chromium \
+	standalone_docker
 
 build: all
 
@@ -71,6 +87,9 @@ chrome_dev:
 chrome_beta:
 	cd ./NodeChrome && docker build $(BUILD_ARGS) $(FROM_IMAGE_ARGS) --build-arg CHROME_VERSION=google-chrome-beta -t $(NAME)/node-chrome:beta .
 
+chromium: node_base
+	cd ./NodeChromium && docker build $(BUILD_ARGS) $(FROM_IMAGE_ARGS) -t $(NAME)/node-chromium:$(TAG_VERSION) .
+
 edge: node_base
 	cd ./NodeEdge && docker build $(BUILD_ARGS) $(FROM_IMAGE_ARGS) -t $(NAME)/node-edge:$(TAG_VERSION) .
 
@@ -113,6 +132,9 @@ standalone_chrome_dev: chrome_dev
 standalone_chrome_beta: chrome_beta
 	cd ./Standalone && docker build $(BUILD_ARGS) --build-arg NAMESPACE=$(NAME) --build-arg VERSION=beta --build-arg BASE=node-chrome -t $(NAME)/standalone-chrome:beta .
 
+standalone_chromium: chromium
+	cd ./Standalone && docker build $(BUILD_ARGS) $(FROM_IMAGE_ARGS) --build-arg BASE=node-chromium -t $(NAME)/standalone-chromium:$(TAG_VERSION) .
+
 standalone_edge: edge
 	cd ./Standalone && docker build $(BUILD_ARGS) $(FROM_IMAGE_ARGS) --build-arg BASE=node-edge -t $(NAME)/standalone-edge:$(TAG_VERSION) .
 
@@ -128,11 +150,14 @@ video:
 
 # https://github.com/SeleniumHQ/docker-selenium/issues/992
 # Additional tags for browser images
-tag_and_push_browser_images: tag_and_push_chrome_images tag_and_push_firefox_images tag_and_push_edge_images
+tag_and_push_browser_images: tag_and_push_chrome_images tag_and_push_firefox_images tag_and_push_edge_images tag_and_push_chromium_images
 
 tag_and_push_chrome_images:
 	./tag_and_push_browser_images.sh $(VERSION) $(BUILD_DATE) $(NAMESPACE) $(PUSH_IMAGE) chrome
 
+tag_and_push_chromium_images:
+	./tag_and_push_browser_images.sh $(VERSION) $(BUILD_DATE) $(NAMESPACE) $(PUSH_IMAGE) chromium
+
 tag_and_push_edge_images:
 	./tag_and_push_browser_images.sh $(VERSION) $(BUILD_DATE) $(NAMESPACE) $(PUSH_IMAGE) edge
 
@@ -149,10 +174,12 @@ tag_latest:
 	docker tag $(NAME)/event-bus:$(TAG_VERSION) $(NAME)/event-bus:latest
 	docker tag $(NAME)/node-base:$(TAG_VERSION) $(NAME)/node-base:latest
 	docker tag $(NAME)/node-chrome:$(TAG_VERSION) $(NAME)/node-chrome:latest
+	docker tag $(NAME)/node-chromium:$(TAG_VERSION) $(NAME)/node-chromium:latest
 	docker tag $(NAME)/node-edge:$(TAG_VERSION) $(NAME)/node-edge:latest
 	docker tag $(NAME)/node-firefox:$(TAG_VERSION) $(NAME)/node-firefox:latest
 	docker tag $(NAME)/node-docker:$(TAG_VERSION) $(NAME)/node-docker:latest
 	docker tag $(NAME)/standalone-chrome:$(TAG_VERSION) $(NAME)/standalone-chrome:latest
+	docker tag $(NAME)/standalone-chromium:$(TAG_VERSION) $(NAME)/standalone-chromium:latest
 	docker tag $(NAME)/standalone-edge:$(TAG_VERSION) $(NAME)/standalone-edge:latest
 	docker tag $(NAME)/standalone-firefox:$(TAG_VERSION) $(NAME)/standalone-firefox:latest
 	docker tag $(NAME)/standalone-docker:$(TAG_VERSION) $(NAME)/standalone-docker:latest
@@ -168,10 +195,12 @@ release_latest:
 	docker push $(NAME)/event-bus:latest
 	docker push $(NAME)/node-base:latest
 	docker push $(NAME)/node-chrome:latest
+	docker push $(NAME)/node-chromium:latest
 	docker push $(NAME)/node-edge:latest
 	docker push $(NAME)/node-firefox:latest
 	docker push $(NAME)/node-docker:latest
 	docker push $(NAME)/standalone-chrome:latest
+	docker push $(NAME)/standalone-chromium:latest
 	docker push $(NAME)/standalone-edge:latest
 	docker push $(NAME)/standalone-firefox:latest
 	docker push $(NAME)/standalone-docker:latest
@@ -187,10 +216,12 @@ tag_major_minor:
 	docker tag $(NAME)/event-bus:$(TAG_VERSION) $(NAME)/event-bus:$(MAJOR)
 	docker tag $(NAME)/node-base:$(TAG_VERSION) $(NAME)/node-base:$(MAJOR)
 	docker tag $(NAME)/node-chrome:$(TAG_VERSION) $(NAME)/node-chrome:$(MAJOR)
+	docker tag $(NAME)/node-chromium:$(TAG_VERSION) $(NAME)/node-chromium:$(MAJOR)
 	docker tag $(NAME)/node-edge:$(TAG_VERSION) $(NAME)/node-edge:$(MAJOR)
 	docker tag $(NAME)/node-firefox:$(TAG_VERSION) $(NAME)/node-firefox:$(MAJOR)
 	docker tag $(NAME)/node-docker:$(TAG_VERSION) $(NAME)/node-docker:$(MAJOR)
 	docker tag $(NAME)/standalone-chrome:$(TAG_VERSION) $(NAME)/standalone-chrome:$(MAJOR)
+	docker tag $(NAME)/standalone-chromium:$(TAG_VERSION) $(NAME)/standalone-chromium:$(MAJOR)
 	docker tag $(NAME)/standalone-edge:$(TAG_VERSION) $(NAME)/standalone-edge:$(MAJOR)
 	docker tag $(NAME)/standalone-firefox:$(TAG_VERSION) $(NAME)/standalone-firefox:$(MAJOR)
 	docker tag $(NAME)/standalone-docker:$(TAG_VERSION) $(NAME)/standalone-docker:$(MAJOR)
@@ -203,10 +234,12 @@ tag_major_minor:
 	docker tag $(NAME)/event-bus:$(TAG_VERSION) $(NAME)/event-bus:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/node-base:$(TAG_VERSION) $(NAME)/node-base:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/node-chrome:$(TAG_VERSION) $(NAME)/node-chrome:$(MAJOR).$(MINOR)
+	docker tag $(NAME)/node-chromium:$(TAG_VERSION) $(NAME)/node-chromium:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/node-edge:$(TAG_VERSION) $(NAME)/node-edge:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/node-firefox:$(TAG_VERSION) $(NAME)/node-firefox:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/node-docker:$(TAG_VERSION) $(NAME)/node-docker:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/standalone-chrome:$(TAG_VERSION) $(NAME)/standalone-chrome:$(MAJOR).$(MINOR)
+	docker tag $(NAME)/standalone-chromium:$(TAG_VERSION) $(NAME)/standalone-chromium:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/standalone-edge:$(TAG_VERSION) $(NAME)/standalone-edge:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/standalone-firefox:$(TAG_VERSION) $(NAME)/standalone-firefox:$(MAJOR).$(MINOR)
 	docker tag $(NAME)/standalone-docker:$(TAG_VERSION) $(NAME)/standalone-docker:$(MAJOR).$(MINOR)
@@ -219,10 +252,12 @@ tag_major_minor:
 	docker tag $(NAME)/event-bus:$(TAG_VERSION) $(NAME)/event-bus:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/node-base:$(TAG_VERSION) $(NAME)/node-base:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/node-chrome:$(TAG_VERSION) $(NAME)/node-chrome:$(MAJOR_MINOR_PATCH)
+	docker tag $(NAME)/node-chromium:$(TAG_VERSION) $(NAME)/node-chromium:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/node-edge:$(TAG_VERSION) $(NAME)/node-edge:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/node-firefox:$(TAG_VERSION) $(NAME)/node-firefox:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/node-docker:$(TAG_VERSION) $(NAME)/node-docker:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/standalone-chrome:$(TAG_VERSION) $(NAME)/standalone-chrome:$(MAJOR_MINOR_PATCH)
+	docker tag $(NAME)/standalone-chromium:$(TAG_VERSION) $(NAME)/standalone-chromium:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/standalone-edge:$(TAG_VERSION) $(NAME)/standalone-edge:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/standalone-firefox:$(TAG_VERSION) $(NAME)/standalone-firefox:$(MAJOR_MINOR_PATCH)
 	docker tag $(NAME)/standalone-docker:$(TAG_VERSION) $(NAME)/standalone-docker:$(MAJOR_MINOR_PATCH)
@@ -237,10 +272,12 @@ release: tag_major_minor
 	@if ! docker images $(NAME)/event-bus | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/event-bus version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/node-base | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-base version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/node-chrome | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-chrome version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
+	@if ! docker images $(NAME)/node-chromium | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-chromium version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/node-edge | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-edge version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/node-firefox | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-firefox version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/node-docker | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/node-docker version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/standalone-chrome | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/standalone-chrome version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
+	@if ! docker images $(NAME)/standalone-chromium | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/standalone-chromium version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/standalone-edge | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/standalone-edge version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/standalone-firefox | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/standalone-firefox version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
 	@if ! docker images $(NAME)/standalone-docker | awk '{ print $$2 }' | grep -q -F $(TAG_VERSION); then echo "$(NAME)/standalone-docker version $(TAG_VERSION) is not yet built. Please run 'make build'"; false; fi
@@ -253,10 +290,12 @@ release: tag_major_minor
 	docker push $(NAME)/event-bus:$(TAG_VERSION)
 	docker push $(NAME)/node-base:$(TAG_VERSION)
 	docker push $(NAME)/node-chrome:$(TAG_VERSION)
+	docker push $(NAME)/node-chromium:$(TAG_VERSION)
 	docker push $(NAME)/node-edge:$(TAG_VERSION)
 	docker push $(NAME)/node-firefox:$(TAG_VERSION)
 	docker push $(NAME)/node-docker:$(TAG_VERSION)
 	docker push $(NAME)/standalone-chrome:$(TAG_VERSION)
+	docker push $(NAME)/standalone-chromium:$(TAG_VERSION)
 	docker push $(NAME)/standalone-edge:$(TAG_VERSION)
 	docker push $(NAME)/standalone-firefox:$(TAG_VERSION)
 	docker push $(NAME)/standalone-docker:$(TAG_VERSION)
@@ -269,10 +308,12 @@ release: tag_major_minor
 	docker push $(NAME)/event-bus:$(MAJOR)
 	docker push $(NAME)/node-base:$(MAJOR)
 	docker push $(NAME)/node-chrome:$(MAJOR)
+	docker push $(NAME)/node-chromium:$(MAJOR)
 	docker push $(NAME)/node-edge:$(MAJOR)
 	docker push $(NAME)/node-firefox:$(MAJOR)
 	docker push $(NAME)/node-docker:$(MAJOR)
 	docker push $(NAME)/standalone-chrome:$(MAJOR)
+	docker push $(NAME)/standalone-chromium:$(MAJOR)
 	docker push $(NAME)/standalone-edge:$(MAJOR)
 	docker push $(NAME)/standalone-firefox:$(MAJOR)
 	docker push $(NAME)/standalone-docker:$(MAJOR)
@@ -285,10 +326,12 @@ release: tag_major_minor
 	docker push $(NAME)/event-bus:$(MAJOR).$(MINOR)
 	docker push $(NAME)/node-base:$(MAJOR).$(MINOR)
 	docker push $(NAME)/node-chrome:$(MAJOR).$(MINOR)
+	docker push $(NAME)/node-chromium:$(MAJOR).$(MINOR)
 	docker push $(NAME)/node-edge:$(MAJOR).$(MINOR)
 	docker push $(NAME)/node-firefox:$(MAJOR).$(MINOR)
 	docker push $(NAME)/node-docker:$(MAJOR).$(MINOR)
 	docker push $(NAME)/standalone-chrome:$(MAJOR).$(MINOR)
+	docker push $(NAME)/standalone-chromium:$(MAJOR).$(MINOR)
 	docker push $(NAME)/standalone-edge:$(MAJOR).$(MINOR)
 	docker push $(NAME)/standalone-firefox:$(MAJOR).$(MINOR)
 	docker push $(NAME)/standalone-docker:$(MAJOR).$(MINOR)
@@ -301,10 +344,12 @@ release: tag_major_minor
 	docker push $(NAME)/event-bus:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/node-base:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/node-chrome:$(MAJOR_MINOR_PATCH)
+	docker push $(NAME)/node-chromium:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/node-edge:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/node-firefox:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/node-docker:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/standalone-chrome:$(MAJOR_MINOR_PATCH)
+	docker push $(NAME)/standalone-chromium:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/standalone-edge:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/standalone-firefox:$(MAJOR_MINOR_PATCH)
 	docker push $(NAME)/standalone-docker:$(MAJOR_MINOR_PATCH)
@@ -315,8 +360,16 @@ test: test_chrome \
  test_chrome_standalone \
  test_firefox_standalone \
  test_edge \
- test_edge_standalone
+ test_edge_standalone \
+ test_chromium \
+ test_chromium_standalone
 
+test_amd64: test
+
+test_arm64: test_chromium \
+ test_firefox \
+ test_chromium_standalone \
+ test_firefox_standalone
 
 test_chrome:
 	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeChrome
@@ -324,6 +377,12 @@ test_chrome:
 test_chrome_standalone:
 	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneChrome
 
+test_chromium:
+	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeChromium
+
+test_chromium_standalone:
+	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneChromium
+
 test_edge:
 	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeEdge
 
@@ -338,17 +397,22 @@ test_firefox_standalone:
 
 # This should run on its own CI job. There is no need to combine it with the other tests.
 # Its main purpose is to check that a video file was generated.
-test_video: video hub chrome firefox edge
+test_video: video hub chrome firefox edge chromium
 	# Running a few tests with docker-compose to generate the videos
-	for node in NodeChrome NodeFirefox NodeEdge ; do \
+	for node in NodeChrome NodeFirefox NodeEdge NodeChromium ; do \
 			cd ./tests || true ; \
 			echo VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) > .env ; \
 			echo TAG=$(TAG_VERSION) >> .env ; \
+			echo NAME=$(NAME) >> .env ; \
 			echo NODE=$$node >> .env ; \
 			if [ $$node = "NodeChrome" ] ; then \
 					echo BROWSER=chrome >> .env ; \
 					echo VIDEO_FILE_NAME=chrome_video.mp4 >> .env ; \
 			fi ; \
+			if [ $$node = "NodeChromium" ] ; then \
+					echo BROWSER=chromium >> .env ; \
+					echo VIDEO_FILE_NAME=chromium_video.mp4 >> .env ; \
+			fi ; \
 			if [ $$node = "NodeEdge" ] ; then \
 					echo BROWSER=edge >> .env ; \
 					echo VIDEO_FILE_NAME=edge_video.mp4 >> .env ; \
@@ -364,6 +428,7 @@ test_video: video hub chrome firefox edge
 	docker run -v $$(pwd):$$(pwd) -w $$(pwd) $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) -v error -i ./tests/videos/chrome_video.mp4 -f null - 2>error.log
 	docker run -v $$(pwd):$$(pwd) -w $$(pwd) $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) -v error -i ./tests/videos/firefox_video.mp4 -f null - 2>error.log
 	docker run -v $$(pwd):$$(pwd) -w $$(pwd) $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) -v error -i ./tests/videos/edge_video.mp4 -f null - 2>error.log
+	docker run -v $$(pwd):$$(pwd) -w $$(pwd) $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) -v error -i ./tests/videos/chromium_video.mp4 -f null - 2>error.log
 
 chart_setup_env:
 	./tests/charts/make/chart_setup_env.sh
@@ -380,7 +445,8 @@ chart_build:
 chart_test: chart_test_template \
  chart_test_chrome \
  chart_test_firefox \
- chart_test_edge
+ chart_test_edge \
+ chart_test_chromium
 
 chart_test_template:
 	./tests/charts/bootstrap.sh
@@ -388,6 +454,9 @@ chart_test_template:
 chart_test_chrome:
 	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/charts/make/chart_test.sh NodeChrome
 
+chart_test_chromium:
+	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/charts/make/chart_test.sh NodeChromium
+
 chart_test_firefox:
 	VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/charts/make/chart_test.sh NodeFirefox
 
@@ -399,10 +468,13 @@ chart_test_parallel_autoscaling:
 
 .PHONY: \
 	all \
+	all_amd64 \
+	all_arm64 \
 	base \
 	build \
 	ci \
 	chrome \
+	chromium \
 	edge \
 	firefox \
 	docker \
@@ -415,10 +487,13 @@ chart_test_parallel_autoscaling:
 	node_base \
 	release \
 	standalone_chrome \
+	standalone_chromium \
 	standalone_edge \
 	standalone_firefox \
 	standalone_docker \
 	tag_latest \
 	tag_and_push_browser_images \
 	test \
+	test_amd64 \
+	test_arm64 \
 	video
diff --git a/NodeBase/start-xvfb.sh b/NodeBase/start-xvfb.sh
index b4959a3902..2d97945090 100755
--- a/NodeBase/start-xvfb.sh
+++ b/NodeBase/start-xvfb.sh
@@ -19,5 +19,5 @@ if [ "${START_XVFB:-$SE_START_XVFB}" = true ] ; then
     --server-args="-screen 0 ${GEOMETRY} -fbdir /var/tmp -dpi ${SCREEN_DPI} -listen tcp -noreset -ac +extension RANDR" \
     /usr/bin/fluxbox -display ${DISPLAY}
 else
-  echo "Xvfb and Fluxbox won't start. Chrome/Firefox/Edge can only run in headless mode. Remember to set the 'headless' flag in your test."
+  echo "Xvfb and Fluxbox won't start. Chrome/Firefox/Edge/Chromium can only run in headless mode. Remember to set the 'headless' flag in your test."
 fi
diff --git a/NodeChromium/Dockerfile b/NodeChromium/Dockerfile
new file mode 100644
index 0000000000..191ba7b8d9
--- /dev/null
+++ b/NodeChromium/Dockerfile
@@ -0,0 +1,55 @@
+ARG NAMESPACE
+ARG VERSION
+ARG AUTHORS
+FROM ${NAMESPACE}/node-base:${VERSION}
+LABEL authors=${AUTHORS}
+
+USER root
+
+#============================================
+# Chromium and chromedriver from Debian
+# Available versions at: https://packages.debian.org/search?keywords=chromium
+#============================================
+COPY chromium.pref /etc/apt/preferences.d/chromium.pref
+
+ARG DEBIAN_VERSION=bullseye
+ARG REPO_FILE=/etc/apt/sources.list.d/debian.list
+ARG KEY_SERVER=keyserver.ubuntu.com
+ARG DEBIAN_KEY_LOCATION=/usr/share/keyrings/debian
+ARG CHROMIUM_VERSION=120.0.6099.109-1~deb11u1
+ARG CHROMEDIVER_VERSION=${CHROMIUM_VERSION}
+RUN mkdir /home/seluser/.gnupg && mkdir -p ${DEBIAN_KEY_LOCATION} \
+  && gpg --no-default-keyring --keyring ${DEBIAN_KEY_LOCATION}/${DEBIAN_VERSION}.gpg \
+         --keyserver ${KEY_SERVER} \
+         --recv-keys 0E98404D386FA1D9 \
+  && echo "deb [signed-by=${DEBIAN_KEY_LOCATION}/${DEBIAN_VERSION}.gpg] http://deb.debian.org/debian ${DEBIAN_VERSION} main" \
+          > ${REPO_FILE} \
+  && gpg --no-default-keyring --keyring ${DEBIAN_KEY_LOCATION}/${DEBIAN_VERSION}-updates.gpg \
+         --keyserver ${KEY_SERVER} \
+         --recv-keys 6ED0E7B82643E131 \
+  && echo "deb [signed-by=${DEBIAN_KEY_LOCATION}/${DEBIAN_VERSION}-updates.gpg] http://deb.debian.org/debian ${DEBIAN_VERSION}-updates main" \
+          >> ${REPO_FILE} \
+  && gpg --no-default-keyring --keyring ${DEBIAN_KEY_LOCATION}/security-${DEBIAN_VERSION}.gpg \
+         --keyserver ${KEY_SERVER} \
+         --recv-keys 112695A0E562B32A \
+  && echo "deb [signed-by=${DEBIAN_KEY_LOCATION}/security-${DEBIAN_VERSION}.gpg] http://deb.debian.org/debian-security ${DEBIAN_VERSION}-security main contrib non-free" \
+          >> ${REPO_FILE} \
+  && apt-get update -qqy \
+  && apt-get -qqy --no-install-recommends install chromium=${CHROMIUM_VERSION} chromium-driver=${CHROMEDIVER_VERSION} \
+  && rm ${REPO_FILE} \
+  && rm -rf /home/seluser/.gnupg ${DEBIAN_KEY_LOCATION}/ /var/lib/apt/lists/* /var/cache/apt/*
+
+#=================================
+# Chromium Launch Script Wrapper
+#=================================
+COPY wrap_chromium_binary /opt/bin/wrap_chromium_binary
+RUN /opt/bin/wrap_chromium_binary
+
+USER ${SEL_UID}
+
+#============================================
+# Dumping Browser information for config
+#============================================
+RUN echo "chrome" > /opt/selenium/browser_name
+RUN chromium --version | awk '{print $2}' > /opt/selenium/browser_version
+RUN echo "\"goog:chromeOptions\": {\"binary\": \"/usr/bin/chromium\"}" > /opt/selenium/browser_binary_location
diff --git a/NodeChromium/chromium.pref b/NodeChromium/chromium.pref
new file mode 100644
index 0000000000..33fb9ace5b
--- /dev/null
+++ b/NodeChromium/chromium.pref
@@ -0,0 +1,14 @@
+# Note: 2 blank lines are required between entries
+Package: *
+Pin: release a=eoan
+Pin-Priority: 500
+
+Package: *
+Pin: origin "deb.debian.org"
+Pin-Priority: 300
+
+# Pattern includes 'chromium', 'chromium-browser' and similarly
+# named dependencies:
+Package: chromium*
+Pin: origin "deb.debian.org"
+Pin-Priority: 700
diff --git a/NodeChromium/wrap_chromium_binary b/NodeChromium/wrap_chromium_binary
new file mode 100755
index 0000000000..0cf5c25334
--- /dev/null
+++ b/NodeChromium/wrap_chromium_binary
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+WRAPPER_PATH=$(readlink -f /usr/bin/chromium)
+BASE_PATH="$WRAPPER_PATH-base"
+mv "$WRAPPER_PATH" "$BASE_PATH"
+
+cat > "$WRAPPER_PATH" <<_EOF
+#!/bin/bash
+
+# umask 002 ensures default permissions of files are 664 (rw-rw-r--) and directories are 775 (rwxrwxr-x).
+umask 002
+
+# Debian/Ubuntu seems to not respect --lang, it instead needs to be a LANGUAGE environment var
+# See: https://stackoverflow.com/a/41893197/359999
+for var in "\$@"; do
+   if [[ \$var == --lang=* ]]; then
+      LANGUAGE=\${var//--lang=}
+   fi
+done
+
+# Set language environment variable
+export LANGUAGE="\$LANGUAGE"
+
+# Note: exec -a below is a bashism.
+exec -a "\$0" "$BASE_PATH" --no-sandbox "\$@"
+_EOF
+chmod +x "$WRAPPER_PATH"
diff --git a/NodeDocker/config.toml b/NodeDocker/config.toml
index 3b63bf66a9..83c1b640a8 100644
--- a/NodeDocker/config.toml
+++ b/NodeDocker/config.toml
@@ -4,6 +4,7 @@
 configs = [
     "selenium/standalone-firefox:4.16.1-20231212", '{"browserName": "firefox", "platformName": "linux"}',
     "selenium/standalone-chrome:4.16.1-20231212", '{"browserName": "chrome", "platformName": "linux"}',
+    "selenium/standalone-chromium:4.16.1-20231212", '{"browserName": "chromium", "platformName": "linux"}',
     "selenium/standalone-edge:4.16.1-20231212", '{"browserName": "MicrosoftEdge", "platformName": "linux"}'
     ]
 
diff --git a/NodeFirefox/Dockerfile b/NodeFirefox/Dockerfile
index 0150b61e34..39c946189b 100644
--- a/NodeFirefox/Dockerfile
+++ b/NodeFirefox/Dockerfile
@@ -8,19 +8,15 @@ USER root
 
 #=========
 # Firefox
+# Available versions at https://launchpad.net/~mozillateam/+archive/ubuntu/ppa
 #=========
-ARG FIREFOX_VERSION=latest
-RUN FIREFOX_DOWNLOAD_URL=$(if [ $FIREFOX_VERSION = "latest" ] || [ $FIREFOX_VERSION = "beta-latest" ] || [ $FIREFOX_VERSION = "nightly-latest" ] || [ $FIREFOX_VERSION = "devedition-latest" ] || [ $FIREFOX_VERSION = "esr-latest" ]; then echo "https://download.mozilla.org/?product=firefox-$FIREFOX_VERSION-ssl&os=linux64&lang=en-US"; else echo "https://download-installer.cdn.mozilla.net/pub/firefox/releases/$FIREFOX_VERSION/linux-x86_64/en-US/firefox-$FIREFOX_VERSION.tar.bz2"; fi) \
-  && apt-get update -qqy \
-  && apt-get -qqy --no-install-recommends install libavcodec-extra \
-     libgtk-3-dev libdbus-glib-1-dev \
-  && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \
-  && wget --no-verbose -O /tmp/firefox.tar.bz2 $FIREFOX_DOWNLOAD_URL \
-  && rm -rf /opt/firefox \
-  && tar -C /opt -xjf /tmp/firefox.tar.bz2 \
-  && rm /tmp/firefox.tar.bz2 \
-  && mv /opt/firefox /opt/firefox-$FIREFOX_VERSION \
-  && ln -fs /opt/firefox-$FIREFOX_VERSION/firefox /usr/bin/firefox
+ARG FIREFOX_VERSION=121.0+build1-0ubuntu0.22.04.1~mt1
+RUN apt-get -qqy update \
+  && apt-get -qqy --no-install-recommends install libavcodec-extra software-properties-common \
+  && add-apt-repository ppa:mozillateam/ppa \
+  && apt-get -qqy update \
+  && apt-get -qqy --no-install-recommends install -t 'o=LP-PPA-mozillateam' firefox=${FIREFOX_VERSION} \
+  && rm -rf /var/lib/apt/lists/* /var/cache/apt/*
 
 #============
 # GeckoDriver
@@ -28,7 +24,13 @@ RUN FIREFOX_DOWNLOAD_URL=$(if [ $FIREFOX_VERSION = "latest" ] || [ $FIREFOX_VERS
 ARG GECKODRIVER_VERSION=latest
 RUN GK_VERSION=$(if [ ${GECKODRIVER_VERSION:-latest} = "latest" ]; then echo "0.33.0"; else echo $GECKODRIVER_VERSION; fi) \
   && echo "Using GeckoDriver version: "$GK_VERSION \
-  && wget --no-verbose -O /tmp/geckodriver.tar.gz https://github.com/mozilla/geckodriver/releases/download/v$GK_VERSION/geckodriver-v$GK_VERSION-linux64.tar.gz \
+  && ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
+  && if [ "$ARCH" = "arm64" ]; then \
+     GK_DOWNLOAD_URL="https://github.com/mozilla/geckodriver/releases/download/v$GK_VERSION/geckodriver-v$GK_VERSION-linux-aarch64.tar.gz" ; \
+  else \
+     GK_DOWNLOAD_URL="https://github.com/mozilla/geckodriver/releases/download/v$GK_VERSION/geckodriver-v$GK_VERSION-linux64.tar.gz" ; \
+  fi \
+  && wget --no-verbose -O /tmp/geckodriver.tar.gz $GK_DOWNLOAD_URL \
   && rm -rf /opt/geckodriver \
   && tar -C /opt -zxf /tmp/geckodriver.tar.gz \
   && rm /tmp/geckodriver.tar.gz \
diff --git a/NodeFirefox/README.md b/NodeFirefox/README.md
new file mode 100644
index 0000000000..c0b0ef1c64
--- /dev/null
+++ b/NodeFirefox/README.md
@@ -0,0 +1,28 @@
+## Building Multi-arch NodeFirefox and StandaloneFirefox
+
+There are two Dockerfiles in NodeFirefox. `Dockerfile` is from the upstream repository for building the standard, official amd64 images. To build `seleniarm/node-firefox` for arm64 or armv7l (or possibly amd64 as well), we use the `Dockerfile.multi-arch` file.
+
+The easiest way to build the image is to use `make`. See examples below:
+
+
+**To build node/firefox for arm64:**
+
+```
+$ NAME=local-seleniarm VERSION=4.5.0 BUILD_DATE=$(date '+%Y%m%d') PLATFORMS=linux/arm64 BUILD_ARGS=--load make firefox_multi
+```
+
+**To build standalone/firefox for arm64:**
+
+```
+$ NAME=local-seleniarm VERSION=4.5.0 BUILD_DATE=$(date '+%Y%m%d') PLATFORMS=linux/arm64 BUILD_ARGS=--load make standalone_firefox_multi
+```
+
+NOTE: Replace PLATFORMS environment variable with `linux/arm/v7` for armv7l/armhf, or `linux/amd64` for amd64.
+
+## Running the standalone image
+
+```
+$ docker run --rm -it --shm-size 2g -p 4444:4444 -p 5900:5900 -p 7900:7900 local-seleniarm/standalone-firefox:latest
+```
+
+As with the x86_64 images from upstream, this also includes noVNC on port 7900, which we can access via http://localhost:7900
diff --git a/build-and-push.sh b/build-and-push.sh
new file mode 100644
index 0000000000..9f237fc8e5
--- /dev/null
+++ b/build-and-push.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+SELENIUM_VERSION=$(grep selenium-server Base/Dockerfile | sed 's/.*-\([^-]*\)\.jar \\/\1/' | head -n 1)
+NAME="${NAME:-seleniarm}"
+VERSION="${VERSION:-$SELENIUM_VERSION}"
+BUILD_DATE="${BUILD_DATE:-$(date '+%Y%m%d')}"
+PLATFORMS="${PLATFORMS:-linux/arm64,linux/arm/v7,linux/amd64}"
+BUILD_ARGS=--push
+
+FROM_IMAGE_ARGS="--build-arg NAMESPACE=$NAME --build-arg VERSION=$VERSION-$BUILD_DATE"
+TAG_VERSION=$VERSION-$BUILD_DATE
+
+START=$(date +'%s')
+echo $START
+
+echo "Build and push images for target $1"
+
+docker run --rm --privileged aptman/qus -- -r
+docker run --rm --privileged aptman/qus -s -- -p
+
+if [ "$1" = "base_multi" ]; then
+    cd ./Base && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} -t ${NAME}/base:${TAG_VERSION} .
+
+elif [ "$1" = "grid_multi" ]; then
+    cd ./Hub && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/hub:${TAG_VERSION} .
+    cd ../Distributor && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/distributor:${TAG_VERSION} .
+    cd ../Router && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/router:${TAG_VERSION} .
+    cd ../Sessions && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/sessions:${TAG_VERSION} .
+    cd ../SessionQueue && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/session-queue:${TAG_VERSION} .
+    cd ../NodeDocker && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/node-docker:${TAG_VERSION} .
+    cd ../EventBus && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/event-bus:${TAG_VERSION} .
+    # Prevent "failed to solve" errors by adding delay between NodeDocker and StandaloneDocker
+    # by building EventBus in between them.
+    cd ../StandaloneDocker && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/standalone-docker:${TAG_VERSION} .
+
+elif [ "$1" = "node_base_multi" ]; then
+    cd ./NodeBase && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/node-base:${TAG_VERSION} .
+
+elif [ "$1" = "firefox_multi" ]; then
+    FROM_IMAGE_ARGS="$FROM_IMAGE_ARGS --build-arg BASE=node-firefox"
+    cd ./NodeFirefox && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -f Dockerfile.multi-arch -t ${NAME}/node-firefox:${TAG_VERSION} .
+    sleep 5   # Prevent "failed to solve" errors when trying to pull NodeFirefox dependency
+    cd ../Standalone && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/standalone-firefox:${TAG_VERSION} .
+
+elif [ "$1" = "chromium_multi" ]; then
+    FROM_IMAGE_ARGS="$FROM_IMAGE_ARGS --build-arg BASE=node-chromium"
+    cd ./NodeChromium && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/node-chromium:${TAG_VERSION} .
+    sleep 5   # Prevent "failed to solve" errors when trying to pull NodeChromium dependency
+    cd ../Standalone && docker buildx build --platform ${PLATFORMS} ${BUILD_ARGS} ${FROM_IMAGE_ARGS} -t ${NAME}/standalone-chromium:${TAG_VERSION} .
+
+elif [ "$1" = "tag_and_push_multi_arch_browser_images" ]; then
+    #make tag_and_push_multi_arch_browser_images
+    echo "Tag images and generate release notes"
+
+else
+    echo "$1 not found. Options are 'base_multi', 'grid_multi', 'node_base_multi', 'firefox_multi', and 'chromium_multi'"
+    SE_BUILD_CODE=1
+fi
+
+SE_BUILD_CODE=${SE_BUILD_CODE:-$(echo $?)}
+
+STOP=$(date +'%s')
+echo $(( $STOP - $START )) seconds
+
+exit $SE_BUILD_CODE
diff --git a/charts/selenium-grid/templates/_helpers.tpl b/charts/selenium-grid/templates/_helpers.tpl
index 48c18bb952..7fbd6e094b 100644
--- a/charts/selenium-grid/templates/_helpers.tpl
+++ b/charts/selenium-grid/templates/_helpers.tpl
@@ -58,6 +58,13 @@ Chrome node fullname
 {{- default "selenium-chrome-node" .Values.chromeNode.nameOverride | trunc 63 | trimSuffix "-" -}}
 {{- end -}}
 
+{{/*
+Chromium node fullname
+*/}}
+{{- define "seleniumGrid.chromiumNode.fullname" -}}
+{{- default "selenium-chromium-node" .Values.chromiumNode.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
 {{/*
 Firefox node fullname
 */}}
diff --git a/charts/selenium-grid/templates/chromium-node-deployment.yaml b/charts/selenium-grid/templates/chromium-node-deployment.yaml
new file mode 100644
index 0000000000..5bdd0fb4a5
--- /dev/null
+++ b/charts/selenium-grid/templates/chromium-node-deployment.yaml
@@ -0,0 +1,30 @@
+{{- if and .Values.chromiumNode.enabled ((eq (include "seleniumGrid.useKEDA" .) "true") | ternary (eq .Values.autoscaling.scalingType "deployment") .Values.chromiumNode.deploymentEnabled) }}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    app: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    app.kubernetes.io/name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    {{- include "seleniumGrid.commonLabels" . | nindent 4 }}
+    {{- with .Values.chromiumNode.labels }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+    {{- with .Values.customLabels }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+spec:
+  {{- if and (not .Values.autoscaling.enabled) (not .Values.autoscaling.enableWithExistingKEDA) }}
+  replicas: {{ .Values.chromiumNode.replicas }}
+  {{end}}
+  selector:
+    matchLabels:
+      app: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+      app.kubernetes.io/instance: {{ .Release.Name }}
+{{- $podScope := deepCopy . -}}
+{{- $_ := set $podScope "name" (include "seleniumGrid.chromiumNode.fullname" .) -}}
+{{- $_ =  set $podScope "node" .Values.chromiumNode -}}
+{{- $_ =  set $podScope "uploader" (get .Values.videoRecorder (.Values.videoRecorder.uploader | toString)) -}}
+{{- include "seleniumGrid.podTemplate" $podScope | nindent 2 }}
+{{- end }}
diff --git a/charts/selenium-grid/templates/chromium-node-hpa.yaml b/charts/selenium-grid/templates/chromium-node-hpa.yaml
new file mode 100644
index 0000000000..8777c003f6
--- /dev/null
+++ b/charts/selenium-grid/templates/chromium-node-hpa.yaml
@@ -0,0 +1,18 @@
+{{- if and .Values.chromiumNode.enabled (eq (include "seleniumGrid.useKEDA" .) "true") (eq .Values.autoscaling.scalingType "deployment") }}
+apiVersion: keda.sh/v1alpha1
+kind: ScaledObject
+metadata:
+  name: selenium-grid-chromium-scaledobject
+  namespace: {{ .Release.Namespace }}
+  annotations:
+    {{- with .Values.autoscaling.annotations }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+  labels:
+    deploymentName: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+spec:
+  {{- $podScope := deepCopy . -}}
+  {{- $_ := set $podScope "name" (include "seleniumGrid.chromiumNode.fullname" .) -}}
+  {{- $_ =  set $podScope "node" .Values.chromiumNode -}}
+  {{- include "seleniumGrid.autoscalingTemplate" $podScope | nindent 2 }}
+{{- end }}
diff --git a/charts/selenium-grid/templates/chromium-node-scaledjobs.yaml b/charts/selenium-grid/templates/chromium-node-scaledjobs.yaml
new file mode 100644
index 0000000000..d7bf26fea1
--- /dev/null
+++ b/charts/selenium-grid/templates/chromium-node-scaledjobs.yaml
@@ -0,0 +1,28 @@
+{{- if and .Values.chromiumNode.enabled (include "seleniumGrid.useKEDA" .) (eq .Values.autoscaling.scalingType "job") }}
+apiVersion: keda.sh/v1alpha1
+kind: ScaledJob
+metadata:
+  name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  annotations:
+    {{- with .Values.autoscaling.annotations }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+  labels:
+    app: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    app.kubernetes.io/name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    {{- include "seleniumGrid.commonLabels" . | nindent 4 }}
+    {{- with .Values.chromiumNode.labels }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+    {{- with .Values.customLabels }}
+      {{- toYaml . | nindent 4 }}
+    {{- end }}
+spec:
+  {{- $podScope := deepCopy . -}}
+  {{- $_ := set $podScope "name" (include "seleniumGrid.chromiumNode.fullname" .) -}}
+  {{- $_ =  set $podScope "node" .Values.chromiumNode -}}
+  {{- $_ =  set $podScope "uploader" (get .Values.videoRecorder (.Values.videoRecorder.uploader | toString)) -}}
+  {{- $_ =  set $podScope "podTemplate" (include "seleniumGrid.podTemplate" $podScope | fromYaml) }}
+  {{- include "seleniumGrid.autoscalingTemplate" $podScope | nindent 2 }}
+{{- end }}
diff --git a/charts/selenium-grid/templates/chromium-node-service.yaml b/charts/selenium-grid/templates/chromium-node-service.yaml
new file mode 100644
index 0000000000..b9982ee21c
--- /dev/null
+++ b/charts/selenium-grid/templates/chromium-node-service.yaml
@@ -0,0 +1,40 @@
+{{- if and .Values.chromiumNode.enabled .Values.chromiumNode.service.enabled }}
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    name: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    {{- include "seleniumGrid.commonLabels" . | nindent 4 }}
+  {{- with .Values.chromiumNode.service.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  type: {{ .Values.chromiumNode.service.type }}
+  selector:
+    app: {{ template "seleniumGrid.chromiumNode.fullname" . }}
+    app.kubernetes.io/instance: {{ .Release.Name }}
+  {{- if and (eq .Values.chromiumNode.service.type "LoadBalancer") (.Values.chromiumNode.service.loadBalancerIP) }}
+  loadBalancerIP: {{ .Values.chromiumNode.service.loadBalancerIP }}
+  {{- end }}
+  ports:
+    - name: tcp-chromium
+      protocol: TCP
+      port: {{ .Values.chromiumNode.seleniumServicePort }}
+      targetPort: {{ .Values.chromiumNode.seleniumPort }}
+  {{- with .Values.chromiumNode.service.ports }}
+    {{- range . }}
+    - name: {{ .name }}
+      port: {{ .port }}
+      targetPort: {{ .targetPort }}
+      {{- if .protocol }}
+      protocol: {{ .protocol }}
+      {{- end }}
+      {{- if and (eq $.Values.chromiumNode.service.type "NodePort") .nodePort }}
+      nodePort: {{ .nodePort }}
+      {{- end }}
+    {{- end }}
+  {{- end }}
+{{- end }}
diff --git a/charts/selenium-grid/values.yaml b/charts/selenium-grid/values.yaml
index 5a82d207de..3489fb5887 100644
--- a/charts/selenium-grid/values.yaml
+++ b/charts/selenium-grid/values.yaml
@@ -796,6 +796,138 @@ edgeNode:
   # It should be set using the --set-json option
   sidecars: []
 
+# Configuration for chromium nodes
+chromiumNode:
+  # Enable chromium nodes
+  enabled: true
+
+  # NOTE: Only used when autoscaling.enabled is false
+  # Enable creation of Deployment
+  # true (default) - if you want long living pods
+  # false - for provisioning your own custom type such as Jobs
+  deploymentEnabled: true
+
+  # Number of chromium nodes
+  replicas: 1
+  # imageRegistry: selenium
+  # Image of chromium nodes
+  imageName: node-chromium
+  # Image of chromium nodes (this overwrites global.seleniumGrid.nodesImageTag)
+  # imageTag: 4.16.1-20231212
+  # Image pull policy (see https://kubernetes.io/docs/concepts/containers/images/#updating-images)
+  imagePullPolicy: IfNotPresent
+  # Image pull secret (see https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/)
+  imagePullSecret: ""
+
+  # Port list to enable on container
+  ports:
+    - 5555
+  # Selenium port (spec.ports[0].targetPort in kubernetes service)
+  seleniumPort: 5900
+  # Selenium port exposed in service (spec.ports[0].port in kubernetes service)
+  seleniumServicePort: 6900
+  # Annotations for chromium-node pods
+  annotations: {}
+  # Labels for chromium-node pods
+  labels: {}
+  # Resources for chromium-node container
+  resources:
+    requests:
+      memory: "1Gi"
+      cpu: "1"
+    limits:
+      memory: "1Gi"
+      cpu: "1"
+  # SecurityContext for chromium-node container
+  securityContext: {}
+  # Tolerations for chromium-node pods
+  tolerations: []
+  # Node selector for chromium-node pods
+  nodeSelector: {}
+  # Custom host aliases for chromium nodes
+  hostAliases:
+  # - ip: "198.51.100.0"
+  #   hostnames:
+  #     - "example.com"
+  #     - "example.net"
+  # - ip: "203.0.113.0"
+  #   hostnames:
+  #     - "example.org"
+  # Custom environment variables for chromium nodes
+  extraEnvironmentVariables:
+  # - name: SE_JAVA_OPTS
+  #   value: "-Xmx512m"
+  # - name:
+  #   valueFrom:
+  #     secretKeyRef:
+  #       name: secret-name
+  #       key: secret-key
+  # Custom environment variables by sourcing entire configMap, Secret, etc. for chromium nodes
+  extraEnvFrom:
+  # - configMapRef:
+  #   name: proxy-settings
+  # - secretRef:
+  #   name: mysecret
+  # Service configuration
+  service:
+    # Create a service for node
+    enabled: true
+    # Service type
+    type: ClusterIP
+    # Set specific loadBalancerIP when serviceType is LoadBalancer (see https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
+    loadBalancerIP: ""
+    # Extra ports exposed in node service
+    ports:
+    # - name: node-port
+    #   port: 5555
+    #   targetPort: 5555
+    # Custom annotations for service
+    annotations: {}
+  # Size limit for DSH volume mounted in container (if not set, default is "1Gi")
+  dshmVolumeSizeLimit: 1Gi
+  # Priority class name for chromium-node pods
+  priorityClassName: ""
+
+  # Wait for pod startup
+  startupProbe: {}
+    # httpGet:
+    #   path: /status
+    #   port: 5555
+  # failureThreshold: 120
+  # periodSeconds: 5
+
+  # Liveness probe settings
+  livenessProbe: {}
+
+  # Time to wait for pod termination
+  terminationGracePeriodSeconds: 30
+  lifecycle: {}
+  extraVolumeMounts: []
+  # - name: my-extra-volume
+  #   mountPath: /home/seluser/Downloads
+
+  extraVolumes: []
+  # - name: my-extra-volume
+  #   emptyDir: {}
+  # - name: my-extra-volume-from-pvc
+  #   persistentVolumeClaim:
+  #     claimName: my-pv-claim
+
+  # Override the scaled options for chromium nodes
+  # scaledOptions:
+  # scaledJobOptions:
+  # scaledObjectOptions:
+  hpa:
+    url: '{{ include "seleniumGrid.graphqlURL" . }}'
+    browserName: chrome
+    # browserVersion: '91.0' # Optional. Only required when supporting multiple versions of browser in your Selenium Grid.
+    unsafeSsl: 'true'  # Optional
+
+  # It is used to add a sidecars proxy in the same pod of the browser node.
+  # It means it will add a new container to the deployment itself.
+  # It should be set using the --set-json option
+  sidecars: []
+
 videoRecorder:
   enabled: false
   # imageRegistry: selenium
diff --git a/docker-add-related-tags.sh b/docker-add-related-tags.sh
new file mode 100644
index 0000000000..1028cc51a0
--- /dev/null
+++ b/docker-add-related-tags.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+NAMESPACE=${NAMESPACE:-seleniarm}
+IMAGE=$1
+TAG=$2
+NO_PULL=$3
+
+echo $NAMESPACE $IMAGE $TAG
+
+RELATED_TAGS=(`go run get-all-related-tags.go https://hub.docker.com/v2/repositories/$NAMESPACE/$IMAGE/tags/$TAG | tail -n 1`)
+
+for related_tag in "${RELATED_TAGS[@]}"
+  do
+    echo Add tag $NAMESPACE/$IMAGE:${related_tag}
+    docker tag $NAMESPACE/$IMAGE:$TAG $NAMESPACE/$IMAGE:$related_tag
+  done
+
diff --git a/docker-compose-v2-tracing.yml b/docker-compose-v2-tracing.yml
index 19583fe13f..888e06a868 100644
--- a/docker-compose-v2-tracing.yml
+++ b/docker-compose-v2-tracing.yml
@@ -50,6 +50,21 @@ services:
     ports:
       - "6902:5900"
 
+
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+      - SE_ENABLE_TRACING=true
+      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-chromium
+    ports:
+      - "6903:5900"
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     ports:
diff --git a/docker-compose-v2.yml b/docker-compose-v2.yml
index 4496c2fb37..b405cccc9a 100644
--- a/docker-compose-v2.yml
+++ b/docker-compose-v2.yml
@@ -39,6 +39,18 @@ services:
     ports:
       - "6902:5900"
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+    ports:
+      - "6903:5900"
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     ports:
diff --git a/docker-compose-v3-basicauth.yml b/docker-compose-v3-basicauth.yml
index af16b19f22..9edadac768 100644
--- a/docker-compose-v3-basicauth.yml
+++ b/docker-compose-v3-basicauth.yml
@@ -33,6 +33,16 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     container_name: selenium-hub
diff --git a/docker-compose-v3-dev.yml b/docker-compose-v3-dev.yml
index b0720fe0ea..3503977c83 100644
--- a/docker-compose-v3-dev.yml
+++ b/docker-compose-v3-dev.yml
@@ -39,6 +39,18 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    volumes:
+      - ./selenium_server_deploy.jar:/opt/selenium/selenium-server.jar
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     container_name: selenium-hub
diff --git a/docker-compose-v3-full-grid-dev.yml b/docker-compose-v3-full-grid-dev.yml
index 6233a1a3f6..1022a6a921 100644
--- a/docker-compose-v3-full-grid-dev.yml
+++ b/docker-compose-v3-full-grid-dev.yml
@@ -109,3 +109,15 @@ services:
       - SE_EVENT_BUS_HOST=selenium-event-bus
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    volumes:
+      - ./selenium_server_deploy.jar:/opt/selenium/selenium-server.jar
+    depends_on:
+      - selenium-event-bus
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-event-bus
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
diff --git a/docker-compose-v3-full-grid-swarm.yml b/docker-compose-v3-full-grid-swarm.yml
index 0d555d75ac..116e158766 100644
--- a/docker-compose-v3-full-grid-swarm.yml
+++ b/docker-compose-v3-full-grid-swarm.yml
@@ -39,6 +39,17 @@ services:
       replicas: 1
     entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+    deploy:
+      replicas: 1
+    entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     ports:
diff --git a/docker-compose-v3-full-grid-tracing.yml b/docker-compose-v3-full-grid-tracing.yml
index bc7db86f32..a303c5e587 100644
--- a/docker-compose-v3-full-grid-tracing.yml
+++ b/docker-compose-v3-full-grid-tracing.yml
@@ -76,7 +76,7 @@ services:
       - SE_SESSIONS_MAP_PORT=5556
       - SE_SESSION_QUEUE_HOST=selenium-session-queue
       - SE_SESSION_QUEUE_PORT=5559
-      - SE_ENABLE_TRACING=true      
+      - SE_ENABLE_TRACING=true
       - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-router
   chrome:
     image: selenium/node-chrome:4.16.1-20231212
@@ -88,7 +88,7 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
       - SE_ENABLE_TRACING=true
-      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-chrome      
+      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-chrome
   edge:
     image: selenium/node-edge:4.16.1-20231212
     shm_size: 2gb
@@ -98,7 +98,7 @@ services:
       - SE_EVENT_BUS_HOST=selenium-event-bus
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
-      - SE_ENABLE_TRACING=true      
+      - SE_ENABLE_TRACING=true
       - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-edge
   firefox:
     image: selenium/node-firefox:4.16.1-20231212
@@ -110,4 +110,15 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
       - SE_ENABLE_TRACING=true
-      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-firefox      
\ No newline at end of file
+      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-firefox
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-event-bus
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-event-bus
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+      - SE_ENABLE_TRACING=true
+      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-chromium
diff --git a/docker-compose-v3-full-grid.yml b/docker-compose-v3-full-grid.yml
index 5bc20ad799..76a21c9389 100644
--- a/docker-compose-v3-full-grid.yml
+++ b/docker-compose-v3-full-grid.yml
@@ -92,4 +92,14 @@ services:
     environment:
       - SE_EVENT_BUS_HOST=selenium-event-bus
       - SE_EVENT_BUS_PUBLISH_PORT=4442
-      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
\ No newline at end of file
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-event-bus
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-event-bus
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
diff --git a/docker-compose-v3-swarm.yml b/docker-compose-v3-swarm.yml
index 0d555d75ac..116e158766 100644
--- a/docker-compose-v3-swarm.yml
+++ b/docker-compose-v3-swarm.yml
@@ -39,6 +39,17 @@ services:
       replicas: 1
     entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+    deploy:
+      replicas: 1
+    entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     ports:
diff --git a/docker-compose-v3-tracing.yml b/docker-compose-v3-tracing.yml
index faf7b2ec4f..938f4da869 100644
--- a/docker-compose-v3-tracing.yml
+++ b/docker-compose-v3-tracing.yml
@@ -44,6 +44,18 @@ services:
       - SE_ENABLE_TRACING=true
       - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-firefox
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+      - SE_ENABLE_TRACING=true
+      - JAVA_OPTS=-Dotel.traces.exporter=jaeger -Dotel.exporter.jaeger.endpoint=http://jaegar:14250 -Dotel.resource.attributes=service.name=selenium-node-chromium
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     container_name: selenium-hub
diff --git a/docker-compose-v3-video.yml b/docker-compose-v3-video.yml
index f216bf8dd0..3f33c8c185 100644
--- a/docker-compose-v3-video.yml
+++ b/docker-compose-v3-video.yml
@@ -33,6 +33,16 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
   chrome_video:
     image: selenium/video:ffmpeg-6.1-20231212
     volumes:
@@ -63,6 +73,16 @@ services:
       - DISPLAY_CONTAINER_NAME=firefox
       - FILE_NAME=firefox_video.mp4
 
+  chromium_video:
+    image: selenium/video:ffmpeg-6.1-20231212
+    volumes:
+      - /tmp/videos:/videos
+    depends_on:
+      - chrome
+    environment:
+      - DISPLAY_CONTAINER_NAME=chromium
+      - FILE_NAME=chromium_video.mp4
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     container_name: selenium-hub
diff --git a/docker-compose-v3.yml b/docker-compose-v3.yml
index b7cfdefcf5..81790ec73c 100644
--- a/docker-compose-v3.yml
+++ b/docker-compose-v3.yml
@@ -33,6 +33,16 @@ services:
       - SE_EVENT_BUS_PUBLISH_PORT=4442
       - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
 
+  chromium:
+    image: selenium/node-chromium:4.16.1-20231212
+    shm_size: 2gb
+    depends_on:
+      - selenium-hub
+    environment:
+      - SE_EVENT_BUS_HOST=selenium-hub
+      - SE_EVENT_BUS_PUBLISH_PORT=4442
+      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
   selenium-hub:
     image: selenium/hub:4.16.1-20231212
     container_name: selenium-hub
diff --git a/generate_multi-arch-release_notes.sh b/generate_multi-arch-release_notes.sh
new file mode 100755
index 0000000000..3167e7db76
--- /dev/null
+++ b/generate_multi-arch-release_notes.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+LATEST_TAG=$1
+HEAD_BRANCH=$2
+GRID_VERSION=$3
+BUILD_DATE=$4
+
+NAMESPACE="${NAMESPACE:-seleniarm}"
+TAG_VERSION=${GRID_VERSION}-${BUILD_DATE}
+
+echo "" >> release_notes.md
+echo "### Changelog" > release_notes.md
+git --no-pager log "${LATEST_TAG}...${HEAD_BRANCH}" --pretty=format:"* [\`%h\`](http://github.com/seleniumhq-community/docker-seleniarm/commit/%H) - %s :: %an" --reverse >> release_notes.md
+
+##############################################################
+# Pull the images so we can populate the release notes
+# We'll pull using the TAG_VERSION and then add the other
+# related tags separately to avoid exceeding the rate limits.
+##############################################################
+docker pull ${NAMESPACE}/base:${TAG_VERSION}
+docker pull ${NAMESPACE}/hub:${TAG_VERSION}
+docker pull ${NAMESPACE}/node-base:${TAG_VERSION}
+docker pull ${NAMESPACE}/standalone-chromium:${TAG_VERSION}
+docker pull ${NAMESPACE}/standalone-firefox:${TAG_VERSION}
+
+docker pull ${NAMESPACE}/node-chromium:${TAG_VERSION}
+docker pull ${NAMESPACE}/node-firefox:${TAG_VERSION}
+docker pull ${NAMESPACE}/node-docker:${TAG_VERSION}
+docker pull ${NAMESPACE}/standalone-docker:${TAG_VERSION}
+docker pull ${NAMESPACE}/sessions:${TAG_VERSION}
+docker pull ${NAMESPACE}/session-queue:${TAG_VERSION}
+docker pull ${NAMESPACE}/event-bus:${TAG_VERSION}
+docker pull ${NAMESPACE}/router:${TAG_VERSION}
+docker pull ${NAMESPACE}/distributor:${TAG_VERSION}
+
+######################################################################
+# Tags are already pushed to Docker Hub, but we need them set locally
+# to generate release notes. Since we know the tags, we can set
+# them locally to avoid exceeding the docker pull rate-limit.
+######################################################################
+bash docker-add-related-tags.sh base ${TAG_VERSION}
+bash docker-add-related-tags.sh hub ${TAG_VERSION}
+bash docker-add-related-tags.sh node-base ${TAG_VERSION}
+bash docker-add-related-tags.sh standalone-chromium ${TAG_VERSION}
+bash docker-add-related-tags.sh standalone-firefox ${TAG_VERSION}
+bash docker-add-related-tags.sh node-chromium ${TAG_VERSION}
+bash docker-add-related-tags.sh node-firefox ${TAG_VERSION}
+bash docker-add-related-tags.sh node-docker ${TAG_VERSION}
+bash docker-add-related-tags.sh standalone-docker ${TAG_VERSION}
+bash docker-add-related-tags.sh sessions ${TAG_VERSION}
+bash docker-add-related-tags.sh session-queue ${TAG_VERSION}
+bash docker-add-related-tags.sh event-bus ${TAG_VERSION}
+bash docker-add-related-tags.sh router ${TAG_VERSION}
+bash docker-add-related-tags.sh distributor ${TAG_VERSION}
+
+CHROMIUM_VERSION=$(docker run --rm ${NAMESPACE}/node-chromium:${TAG_VERSION} chromium --version | awk '{print $2}')
+CHROMEDRIVER_VERSION=$(docker run --rm ${NAMESPACE}/node-chromium:${TAG_VERSION} chromedriver --version | awk '{print $2}')
+FIREFOX_VERSION=$(docker run --rm ${NAMESPACE}/node-firefox:${TAG_VERSION} firefox --version | awk '{print $3}')
+GECKODRIVER_VERSION=$(docker run --rm ${NAMESPACE}/node-firefox:${TAG_VERSION} geckodriver --version | awk 'NR==1{print $2}')
+
+echo "" >> release_notes.md
+echo "### Released versions" >> release_notes.md
+echo "* Selenium: ${GRID_VERSION}" >> release_notes.md
+echo "* Chromium: ${CHROMIUM_VERSION}" >> release_notes.md
+echo "* ChromiumDriver: ${CHROMEDRIVER_VERSION}" >> release_notes.md
+echo "* Firefox: ${FIREFOX_VERSION}" >> release_notes.md
+echo "* GeckoDriver: ${GECKODRIVER_VERSION}" >> release_notes.md
+
+echo "" >> release_notes.md
+echo "### Published Docker images" >> release_notes.md
+echo "<details>" >> release_notes.md
+echo "<summary>Click to see published Docker images</summary>" >> release_notes.md
+echo "" >> release_notes.md
+echo '```' >> release_notes.md
+docker images --filter=reference="seleniarm/*:*" --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.Size}}" >> release_notes.md
+echo '```' >> release_notes.md
+echo "" >> release_notes.md
+echo "</details>" >> release_notes.md
diff --git a/generate_release_notes.sh b/generate_release_notes.sh
index c2f4e40aeb..c17fbe344d 100755
--- a/generate_release_notes.sh
+++ b/generate_release_notes.sh
@@ -12,8 +12,10 @@ echo "### Changelog" > release_notes.md
 git --no-pager log "${LATEST_TAG}...${HEAD_BRANCH}" --pretty=format:"* [\`%h\`](http://github.com/seleniumhq/docker-selenium/commit/%H) - %s :: %an" --reverse >> release_notes.md
 
 CHROME_VERSION=$(docker run --rm selenium/node-chrome:${TAG_VERSION} google-chrome --version | awk '{print $3}')
+CHROMIUM_VERSION=$(docker run --rm selenium/node-chromium:${TAG_VERSION} chromium --version | awk '{print $2}')
 EDGE_VERSION=$(docker run --rm selenium/node-edge:${TAG_VERSION} microsoft-edge --version | awk '{print $3}')
 CHROMEDRIVER_VERSION=$(docker run --rm selenium/node-chrome:${TAG_VERSION} chromedriver --version | awk '{print $2}')
+CHROMIUMDRIVER_VERSION=$(docker run --rm selenium/node-chromium:${TAG_VERSION} chromedriver --version | awk '{print $2}')
 EDGEDRIVER_VERSION=$(docker run --rm selenium/node-edge:${TAG_VERSION} msedgedriver --version | awk '{print $4}')
 FIREFOX_VERSION=$(docker run --rm selenium/node-firefox:${TAG_VERSION} firefox --version | awk '{print $3}')
 GECKODRIVER_VERSION=$(docker run --rm selenium/node-firefox:${TAG_VERSION} geckodriver --version | awk 'NR==1{print $2}')
@@ -29,6 +31,8 @@ echo "* Edge: ${EDGE_VERSION}" >> release_notes.md
 echo "* EdgeDriver: ${EDGEDRIVER_VERSION}" >> release_notes.md
 echo "* Firefox: ${FIREFOX_VERSION}" >> release_notes.md
 echo "* GeckoDriver: ${GECKODRIVER_VERSION}" >> release_notes.md
+echo "* Chromium: ${CHROMIUM_VERSION}" >> release_notes.md
+echo "* ChromiumDriver: ${CHROMIUMDRIVER_VERSION}" >> release_notes.md
 echo "* ffmpeg: ${FFMPEG_VERSION}" >> release_notes.md
 
 echo "" >> release_notes.md
diff --git a/get-access-token.py b/get-access-token.py
new file mode 100644
index 0000000000..630783bf68
--- /dev/null
+++ b/get-access-token.py
@@ -0,0 +1,40 @@
+import time
+from cryptography.hazmat.backends import default_backend
+import jwt
+import os
+import requests
+import logging
+
+github_app_id = os.environ.get('GITHUB_APP_ID')
+github_installation_id = os.environ.get('GITHUB_INSTALLATION_ID')
+private_key = os.environ.get('GITHUB_APP_PEM')
+private_key = private_key.replace("\\n", "\n")
+
+standard_error_msg = 'Seleniarm GitHub App installation environment variables are not set. '
+if github_app_id == '':
+  raise Exception(standard_error_msg + 'Valid GITHUB_APP_ID is required to obtain an access token.')
+if github_installation_id == '':
+  raise Exception(standard_error_msg + 'Valid GITHUB_INSTALLATION_ID is required to obtain an access token.')
+if private_key == '':
+  raise Exception(standard_error_msg + 'Valid GITHUB_APP_PEM token is required to obtain an access token.')
+
+
+time_since_epoch_in_seconds = int(time.time())
+    
+payload = {
+  # issued at time
+  'iat': time_since_epoch_in_seconds,
+  # JWT expiration time (10 minute maximum)
+  'exp': time_since_epoch_in_seconds + (10 * 60),
+  # GitHub App's identifier
+  'iss': github_app_id
+}
+
+actual_jwt = jwt.encode(payload, private_key, algorithm='RS256')
+
+headers = {"Authorization": "Bearer " + actual_jwt,
+           "Accept": "application/vnd.github.v3+json"}
+
+resp = requests.post('https://api.github.com/app/installations/' + github_installation_id + '/access_tokens', headers=headers)
+
+print(resp.json()['token'])
diff --git a/get-access-token.sh b/get-access-token.sh
new file mode 100644
index 0000000000..3fc7c6daa2
--- /dev/null
+++ b/get-access-token.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+python3 -m venv release_sandbox
+. release_sandbox/bin/activate
+if [ -z "$VIRTUAL_ENV" ]; then
+	echo "Virtual environment not activated."
+	exit 1
+fi
+
+pip3 install cryptography
+pip3 install requests
+pip3 install PyJWT
+
+export GITHUB_APP_ID="$SELENIARM_GITHUB_APP_ID"
+export GITHUB_INSTALLATION_ID="$SELENIARM_GITHUB_INSTALLATION_ID"
+export GITHUB_APP_PEM="$SELENIARM_GITHUB_APP_PEM" 
+
+python3 get-access-token.py
diff --git a/get-all-related-tags.go b/get-all-related-tags.go
new file mode 100644
index 0000000000..650a9152bf
--- /dev/null
+++ b/get-all-related-tags.go
@@ -0,0 +1,179 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+)
+
+type Latest struct {
+	Images []struct {
+		Architecture string `json:"architecture"`
+		Digest       string `json:"digest"`
+	}
+}
+
+type Result struct {
+	Results []struct {
+		Images []struct {
+			Digest string `json:"digest"`
+		}
+		Name string `json:"name"`
+	}
+}
+
+func main() {
+	argLen := len(os.Args)
+	if argLen < 2 {
+		showUsage()
+		os.Exit(1)
+	}
+	url := os.Args[1] // https://hub.docker.com/v2/repositories/selenium/standalone-chrome/tags/latest/
+	var inputArch string
+	if argLen >= 3 {
+		inputArch = os.Args[2]
+	}
+
+	if inputArch == "" {
+		fmt.Println("Get related tags using sha256 for random architecture...")
+	} else {
+		fmt.Println("Get related tags using sha256 for " + inputArch + " architecture...")
+	}
+
+	arch, tagDigest := getDigestForTag(url, inputArch)
+	fmt.Println("getting related tags for " + arch + " digest = " + tagDigest)
+
+	var allTagsUrl string
+	var theseRelatedTags []string
+	var relatedTagsStr string
+
+	allTagsUrl = getAllTagsUrl(url)
+	theseRelatedTags = getRelatedTagsFromDigest(allTagsUrl, tagDigest)
+	relatedTagsStr = strings.Join(theseRelatedTags, " ")
+	for next := true; next; next = (len(theseRelatedTags) != 0) {
+		allTagsUrl = getAllTagsUrlForNextPage(allTagsUrl)
+		theseRelatedTags = getRelatedTagsFromDigest(allTagsUrl, tagDigest)
+		relatedTagsStr = relatedTagsStr + " " + strings.Join(theseRelatedTags, " ")
+		// fmt.Println(theseRelatedTags)
+	}
+
+	fmt.Println(relatedTagsStr)
+}
+
+func getDigestForTag(url string, inputArch string) (string, string) {
+	resp, err := http.Get(url)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	var jsonResp Latest
+	sb := string(body)
+	json.Unmarshal([]byte(sb), &jsonResp)
+
+	var digest string
+	var arch string
+	if inputArch != "" {
+		for _, image := range jsonResp.Images {
+			if inputArch == image.Architecture {
+				digest = image.Digest
+				arch = image.Architecture
+			}
+		}
+	} else {
+		for _, image := range jsonResp.Images {
+			digest = image.Digest
+			arch = image.Architecture
+		}
+	}
+
+	return arch, digest
+}
+
+func getAllTagsUrlForNextPage(specificTagUrl string) string {
+	urlPathArr := strings.Split(specificTagUrl, "?")
+	var pageNum string
+	if len(urlPathArr) > 1 {
+		pageKeyVal := strings.Split(urlPathArr[1], "=")
+		pageNumInt, err := strconv.Atoi(pageKeyVal[1])
+		if err != nil {
+			log.Fatalln(err)
+		}
+		pageNum = "?page=" + strconv.Itoa(pageNumInt+1)
+	} else {
+		pageNum = "?page=1"
+	}
+	return urlPathArr[0] + pageNum
+}
+
+func getAllTagsUrl(specificTagUrl string) string {
+	urlPathArr := strings.Split(specificTagUrl, "?")
+	var pageNum string
+	if len(urlPathArr) > 1 {
+		pageNum = "?" + urlPathArr[1]
+	} else {
+		pageNum = "?page=1"
+	}
+
+	specificTagUrlWithoutPage := urlPathArr[0]
+	urlArr := strings.Split(specificTagUrlWithoutPage, "/")
+	// fmt.Println(urlArr[len(urlArr)-1])
+	if urlArr[len(urlArr)-1] == "" {
+		urlArr[len(urlArr)-1] = ""
+		urlArr[len(urlArr)-2] = ""
+	} else {
+		urlArr[len(urlArr)-1] = ""
+	}
+	urlArr = urlArr[:len(urlArr)-1]
+	allTagsUrl := strings.Join(urlArr, "/") // // https://hub.docker.com/v2/repositories/selenium/standalone-chrome/tags/
+	return allTagsUrl + pageNum
+}
+
+func getRelatedTagsFromDigest(allTagsUrl string, digest string) []string {
+
+	resp, err := http.Get(allTagsUrl)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	var jsonResp Result
+	sb := string(body)
+	json.Unmarshal([]byte(sb), &jsonResp)
+
+	var relatedTags []string
+	for _, results := range jsonResp.Results {
+		for _, image := range results.Images {
+			imageDigest := image.Digest
+			if imageDigest == digest {
+				relatedTags = append(relatedTags, results.Name)
+			}
+		}
+	}
+	return relatedTags
+}
+
+func showUsage() {
+	fmt.Println(`Usage: 
+    get-all-related-tags TAG_URL [ARCH]
+
+    TAG_URL -> URL for a container image manifest (Required)
+    ARCH    -> Architecture to use to obtain sha256 (Optional)
+
+    Example Usage:
+    $ get-all-related-tags https://hub.docker.com/v2/repositories/selenium/standalone-chrome/tags/latest/
+    `)
+}
diff --git a/get-image-sha256-digest.go b/get-image-sha256-digest.go
new file mode 100644
index 0000000000..d51b58ca5c
--- /dev/null
+++ b/get-image-sha256-digest.go
@@ -0,0 +1,56 @@
+package main 
+
+import (
+	"io/ioutil"
+	"log"
+	"net/http"
+	"fmt"
+	"encoding/json"
+	"os"
+)
+
+type Latest struct {
+	Images []struct {
+		Architecture string `json:"architecture"`
+                Digest string `json:"digest"`
+	}
+}
+
+
+func main() {
+    argLen := len(os.Args)
+    if argLen < 2 {
+	    showUsage()
+	    os.Exit(1)
+    }
+
+    url := os.Args[1]   // https://hub.docker.com/v2/repositories/selenium/standalone-chrome/tags/latest/
+    resp, err := http.Get(url)
+    if err != nil {
+	log.Fatalln(err)
+    }
+
+    body, err := ioutil.ReadAll(resp.Body)
+    if err != nil {
+	    log.Fatalln(err)
+    }
+
+    var jsonResp Latest
+    sb := string(body)
+    json.Unmarshal([]byte(sb), &jsonResp)
+    
+    for _, image := range jsonResp.Images {
+        fmt.Printf(image.Architecture + " " + image.Digest + "\n")
+    }    
+}
+
+func showUsage() {
+	fmt.Println(`Usage: 
+    get-image-sha256-digest TAG_URL
+
+    TAG_URL -> URL for a container image manifest (Required)
+
+    Example Usage:
+    $ get-image-sha256-digest https://hub.docker.com/v2/repositories/selenium/standalone-chrome/tags/latest/
+    `)
+}
diff --git a/tag-and-push-multi-arch-image.sh b/tag-and-push-multi-arch-image.sh
new file mode 100755
index 0000000000..7e993dced9
--- /dev/null
+++ b/tag-and-push-multi-arch-image.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+VERSION=$1
+BUILD_DATE=$2
+NAMESPACE="${3:-seleniarm}"
+IMAGE=$4
+NEW_TAG=$5
+
+if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ] || [ -z "$5" ]; then
+  echo "Be sure to pass in all of the values"
+  exit 1
+fi
+
+
+AMD64_DIGEST=`go run get-image-sha256-digest.go https://hub.docker.com/v2/repositories/$NAMESPACE/$IMAGE/tags/$VERSION-$BUILD_DATE/ | grep -w "amd64" | awk '{print $2}'`
+ARM_DIGEST=`go run get-image-sha256-digest.go https://hub.docker.com/v2/repositories/$NAMESPACE/$IMAGE/tags/$VERSION-$BUILD_DATE/ | grep -w "arm" | awk '{print $2}'`
+ARM64_DIGEST=`go run get-image-sha256-digest.go https://hub.docker.com/v2/repositories/$NAMESPACE/$IMAGE/tags/$VERSION-$BUILD_DATE/ | grep -w "arm64" | awk '{print $2}'`
+
+docker manifest create $NAMESPACE/$IMAGE:$NEW_TAG \
+  --amend $NAMESPACE/$IMAGE@$AMD64_DIGEST \
+  --amend $NAMESPACE/$IMAGE@$ARM_DIGEST \
+  --amend $NAMESPACE/$IMAGE@$ARM64_DIGEST
+
+docker manifest push $NAMESPACE/$IMAGE:$NEW_TAG
+
diff --git a/tag_and_push_multi-arch_browser_images.sh b/tag_and_push_multi-arch_browser_images.sh
new file mode 100755
index 0000000000..f276f6e5a2
--- /dev/null
+++ b/tag_and_push_multi-arch_browser_images.sh
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+
+VERSION=$1
+BUILD_DATE=$2
+NAMESPACE=$3
+PUSH_IMAGE="${4:-false}"
+BROWSER=$5
+
+TAG_VERSION=${VERSION}-${BUILD_DATE}
+
+function short_version() {
+    local __long_version=$1
+    local __version_split=( ${__long_version//./ } )
+    echo "${__version_split[0]}.${__version_split[1]}"
+}
+
+MAJOR=$(cut -d. -f1 <<<"${VERSION}")
+MAJOR_MINOR=$(cut -d. -f1-2 <<<"${VERSION}")
+
+echo "Tagging images for browser ${BROWSER}, version ${VERSION}, build date ${BUILD_DATE}, namespace ${NAMESPACE}"
+
+case "${BROWSER}" in
+
+chromium)
+  CHROMIUM_VERSION=$(docker run --rm ${NAMESPACE}/node-chromium:${TAG_VERSION} chromium --version | awk '{print $2}')
+  echo "Chromium version -> "${CHROMIUM_VERSION}
+  CHROME_SHORT_VERSION="$(short_version ${CHROMIUM_VERSION})"
+  echo "Short Chromium version -> "${CHROME_SHORT_VERSION}
+
+  CHROMEDRIVER_VERSION=$(docker run --rm ${NAMESPACE}/node-chromium:${TAG_VERSION} chromedriver --version | awk '{print $2}')
+  echo "ChromeDriver version -> "${CHROMEDRIVER_VERSION}
+  CHROMEDRIVER_SHORT_VERSION="$(short_version ${CHROMEDRIVER_VERSION})"
+  echo "Short ChromeDriver version -> "${CHROMEDRIVER_SHORT_VERSION}
+
+  CHROME_TAGS=(
+      # Major Selenium version (X)
+      ${MAJOR}
+      # Major-minor Selenium version (X.Y)
+      ${MAJOR_MINOR}
+      # Full Selenium version (X.Y.X)
+      ${VERSION}
+      ${CHROMIUM_VERSION}-chromedriver-${CHROMEDRIVER_VERSION}-grid-${TAG_VERSION}
+      # Browser version and browser driver version plus build date
+      ${CHROMIUM_VERSION}-chromedriver-${CHROMEDRIVER_VERSION}-${BUILD_DATE}
+      # Browser version and browser driver version
+      ${CHROMIUM_VERSION}-chromedriver-${CHROMEDRIVER_VERSION}      
+      # Browser version and build date
+      ${CHROMIUM_VERSION}-${BUILD_DATE}
+      # Browser version
+      ${CHROMIUM_VERSION}      
+      ## Short versions
+      ${CHROME_SHORT_VERSION}-chromedriver-${CHROMEDRIVER_SHORT_VERSION}-grid-${TAG_VERSION}
+      # Browser version and browser driver version plus build date
+      ${CHROME_SHORT_VERSION}-chromedriver-${CHROMEDRIVER_SHORT_VERSION}-${BUILD_DATE}
+      # Browser version and browser driver version
+      ${CHROME_SHORT_VERSION}-chromedriver-${CHROMEDRIVER_SHORT_VERSION}      
+      # Browser version and build date
+      ${CHROME_SHORT_VERSION}-${BUILD_DATE}
+      # Browser version
+      ${CHROME_SHORT_VERSION}
+  )
+
+  for chrome_tag in "${CHROME_TAGS[@]}"
+  do
+    if [ "${PUSH_IMAGE}" = true ]; then
+        docker buildx imagetools create -t ${NAMESPACE}/node-chromium:${chrome_tag} ${NAMESPACE}/node-chromium:${TAG_VERSION}
+        docker buildx imagetools create -t ${NAMESPACE}/standalone-chromium:${chrome_tag} ${NAMESPACE}/standalone-chromium:${TAG_VERSION}
+        #sh tag-and-push-multi-arch-image.sh $VERSION $BUILD_DATE $NAMESPACE node-chromium ${chrome_tag}
+        #sh tag-and-push-multi-arch-image.sh $VERSION $BUILD_DATE $NAMESPACE standalone-chromium ${chrome_tag}
+    fi
+  done
+  
+  ;;
+firefox)
+  FIREFOX_VERSION=$(docker run --rm ${NAMESPACE}/node-firefox:${TAG_VERSION} firefox --version | awk '{print $3}')
+  echo "Firefox version -> "${FIREFOX_VERSION}
+  FIREFOX_SHORT_VERSION="$(short_version ${FIREFOX_VERSION})"
+  echo "Short Firefox version -> "${FIREFOX_SHORT_VERSION}
+  GECKODRIVER_VERSION=$(docker run --rm ${NAMESPACE}/node-firefox:${TAG_VERSION} geckodriver --version | awk 'NR==1{print $2}')
+  echo "GeckoDriver version -> "${GECKODRIVER_VERSION}
+  GECKODRIVER_SHORT_VERSION="$(short_version ${GECKODRIVER_VERSION})"
+  echo "Short GeckoDriver version -> "${GECKODRIVER_SHORT_VERSION}
+
+  FIREFOX_TAGS=(
+      # Major Selenium version (X)
+      ${MAJOR}
+      # Major-minor Selenium version (X.Y)
+      ${MAJOR_MINOR}
+      # Full Selenium version (X.Y.X)
+      ${VERSION}
+      ${FIREFOX_VERSION}-geckodriver-${GECKODRIVER_VERSION}-grid-${TAG_VERSION}
+      # Browser version and browser driver version plus build date
+      ${FIREFOX_VERSION}-geckodriver-${GECKODRIVER_VERSION}-${BUILD_DATE}
+      # Browser version and browser driver version
+      ${FIREFOX_VERSION}-geckodriver-${GECKODRIVER_VERSION}      
+      # Browser version and build date
+      ${FIREFOX_VERSION}-${BUILD_DATE}
+      # Browser version
+      ${FIREFOX_VERSION}      
+      ## Short versions
+      ${FIREFOX_SHORT_VERSION}-geckodriver-${GECKODRIVER_SHORT_VERSION}-grid-${TAG_VERSION}
+      # Browser version and browser driver version plus build date
+      ${FIREFOX_SHORT_VERSION}-geckodriver-${GECKODRIVER_SHORT_VERSION}-${BUILD_DATE}
+      # Browser version and browser driver version
+      ${FIREFOX_SHORT_VERSION}-geckodriver-${GECKODRIVER_SHORT_VERSION}      
+      # Browser version and build date
+      ${FIREFOX_SHORT_VERSION}-${BUILD_DATE}
+      # Browser version
+      ${FIREFOX_SHORT_VERSION}
+  )
+
+  for firefox_tag in "${FIREFOX_TAGS[@]}"
+  do
+    if [ "${PUSH_IMAGE}" = true ]; then
+        docker buildx imagetools create -t ${NAMESPACE}/node-firefox:${firefox_tag} ${NAMESPACE}/node-firefox:${TAG_VERSION}
+        docker buildx imagetools create -t ${NAMESPACE}/standalone-firefox:${firefox_tag} ${NAMESPACE}/standalone-firefox:${TAG_VERSION}
+        #sh tag-and-push-multi-arch-image.sh $VERSION $BUILD_DATE $NAMESPACE node-firefox ${firefox_tag}
+        #sh tag-and-push-multi-arch-image.sh $VERSION $BUILD_DATE $NAMESPACE standalone-firefox ${firefox_tag}
+    fi
+  done
+
+  ;;
+*)
+  echo "Unknown browser!"
+  ;;
+esac
diff --git a/tag_and_push_multi-arch_major_minor.sh b/tag_and_push_multi-arch_major_minor.sh
new file mode 100755
index 0000000000..b7f563754c
--- /dev/null
+++ b/tag_and_push_multi-arch_major_minor.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+VERSION=$1
+BUILD_DATE=$2
+NAMESPACE=$3
+PUSH_IMAGE="${4:-false}"
+IMAGE=$5
+
+TAG_VERSION=${VERSION}-${BUILD_DATE}
+
+MAJOR=$(cut -d. -f1 <<<"${VERSION}")
+MAJOR_MINOR=$(cut -d. -f1-2 <<<"${VERSION}")
+
+TAGS=(
+    $MAJOR
+    $MAJOR_MINOR
+    $VERSION
+)
+
+for tag in "${TAGS[@]}"
+  do
+    if [ "${PUSH_IMAGE}" = true ]; then
+        docker buildx imagetools create -t ${NAMESPACE}/${IMAGE}:${tag} ${NAMESPACE}/${IMAGE}:${TAG_VERSION}
+        #sh tag-and-push-multi-arch-image.sh $VERSION $BUILD_DATE $NAMESPACE $IMAGE ${tag}
+    fi
+  done
diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py
index fa95acefaf..c74b0d98b9 100644
--- a/tests/SeleniumTests/__init__.py
+++ b/tests/SeleniumTests/__init__.py
@@ -80,7 +80,7 @@ def test_download_file(self):
         if is_continue:
             file_link.click()
             wait.until(
-                lambda d: str(d.get_downloadable_files()[0]).endswith(file_name)
+                lambda d: len(d.get_downloadable_files()) > 0 and str(d.get_downloadable_files()[0]).endswith(file_name)
             )
             self.assertTrue(str(driver.get_downloadable_files()[0]).endswith(file_name))
 
diff --git a/tests/charts/ci/NodeChrome-values.yaml b/tests/charts/ci/NodeChrome-values.yaml
index 09396f42d0..f92bfd952b 100644
--- a/tests/charts/ci/NodeChrome-values.yaml
+++ b/tests/charts/ci/NodeChrome-values.yaml
@@ -5,6 +5,9 @@ chromeNode:
   extraEnvironmentVariables:
     - name: SE_OPTS
       value: "--enable-managed-downloads true"
+# Configuration for chromium nodes
+chromiumNode:
+  enabled: false
 # Configuration for edge nodes
 edgeNode:
   enabled: false
diff --git a/tests/charts/ci/NodeChromium-values.yaml b/tests/charts/ci/NodeChromium-values.yaml
new file mode 100644
index 0000000000..fc015db606
--- /dev/null
+++ b/tests/charts/ci/NodeChromium-values.yaml
@@ -0,0 +1,16 @@
+# This is used in Helm chart testing
+# Configuration for chrome nodes
+chromeNode:
+  enabled: false
+# Configuration for chromium nodes
+chromiumNode:
+  nameOverride: my-chromium-name
+  extraEnvironmentVariables:
+    - name: SE_OPTS
+      value: "--enable-managed-downloads true"
+# Configuration for edge nodes
+edgeNode:
+  enabled: false
+# Configuration for firefox nodes
+firefoxNode:
+  enabled: false
diff --git a/tests/charts/ci/NodeEdge-values.yaml b/tests/charts/ci/NodeEdge-values.yaml
index 27220e2759..d90b7707db 100644
--- a/tests/charts/ci/NodeEdge-values.yaml
+++ b/tests/charts/ci/NodeEdge-values.yaml
@@ -2,6 +2,9 @@
 # Configuration for chrome nodes
 chromeNode:
   enabled: false
+# Configuration for chromium nodes
+chromiumNode:
+  enabled: false
 # Configuration for edge nodes
 edgeNode:
   nameOverride: my-edge-name
diff --git a/tests/charts/ci/NodeFirefox-values.yaml b/tests/charts/ci/NodeFirefox-values.yaml
index 21bb6a2856..04822a5c24 100644
--- a/tests/charts/ci/NodeFirefox-values.yaml
+++ b/tests/charts/ci/NodeFirefox-values.yaml
@@ -2,6 +2,9 @@
 # Configuration for chrome nodes
 chromeNode:
   enabled: false
+# Configuration for chromium nodes
+chromiumNode:
+  enabled: false
 # Configuration for edge nodes
 edgeNode:
   enabled: false
diff --git a/tests/charts/ci/ParallelAutoscaling-values.yaml b/tests/charts/ci/ParallelAutoscaling-values.yaml
index 39ea9434c8..2e1d428196 100644
--- a/tests/charts/ci/ParallelAutoscaling-values.yaml
+++ b/tests/charts/ci/ParallelAutoscaling-values.yaml
@@ -9,6 +9,11 @@ chromeNode:
   extraEnvironmentVariables:
     - name: SE_OPTS
       value: "--enable-managed-downloads true"
+chromiumNode:
+  nameOverride: my-chromium-name
+  extraEnvironmentVariables:
+    - name: SE_OPTS
+      value: "--enable-managed-downloads true"
 # Configuration for edge nodes
 edgeNode:
   nameOverride: my-edge-name
diff --git a/tests/charts/templates/render/dummy.yaml b/tests/charts/templates/render/dummy.yaml
index 52d672a8ac..185cc41919 100644
--- a/tests/charts/templates/render/dummy.yaml
+++ b/tests/charts/templates/render/dummy.yaml
@@ -48,6 +48,9 @@ components:
 chromeNode:
   affinity: *affinity
 
+chromiumNode:
+  affinity: *affinity
+
 firefoxNode:
     affinity: *affinity
 
diff --git a/tests/charts/templates/test.py b/tests/charts/templates/test.py
index f05c7629d7..08ea8d8a9b 100644
--- a/tests/charts/templates/test.py
+++ b/tests/charts/templates/test.py
@@ -17,7 +17,7 @@ def load_template(yaml_file):
 
 class ChartTemplateTests(unittest.TestCase):
     def test_set_affinity(self):
-        resources_name = ['selenium-chrome-node', 'selenium-distributor', 'selenium-edge-node', 'selenium-firefox-node',
+        resources_name = ['selenium-chrome-node', 'selenium-distributor', 'selenium-edge-node', 'selenium-firefox-node', 'selenium-chromium-node',
                 'selenium-event-bus', 'selenium-router', 'selenium-session-map', 'selenium-session-queue']
         count = 0
         logger.info(f"Assert affinity is set in global and nodes")
diff --git a/tests/docker-compose-v3-test-video.yml b/tests/docker-compose-v3-test-video.yml
index bbfb6f6abe..dd07a4b982 100644
--- a/tests/docker-compose-v3-test-video.yml
+++ b/tests/docker-compose-v3-test-video.yml
@@ -4,7 +4,7 @@
 version: "3"
 services:
   browser:
-    image: selenium/node-${BROWSER}:${TAG}
+    image: ${NAME}/node-${BROWSER}:${TAG}
     shm_size: 2gb
     depends_on:
       - selenium-hub
@@ -17,7 +17,7 @@ services:
       - "6900:5900"
 
   browser_video:
-    image: selenium/video:${VIDEO_TAG}
+    image: ${NAME}/video:${VIDEO_TAG}
     volumes:
       - ./videos:/videos
     depends_on:
@@ -27,7 +27,7 @@ services:
       - FILE_NAME=${VIDEO_FILE_NAME}
 
   selenium-hub:
-    image: selenium/hub:${TAG}
+    image: ${NAME}/hub:${TAG}
     container_name: selenium-hub
     ports:
       - "4442:4442"
diff --git a/tests/test.py b/tests/test.py
index 0d260e3300..02fb810a01 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -41,6 +41,10 @@
     # Firefox Images
     'NodeFirefox': 'node-firefox',
     'StandaloneFirefox': 'standalone-firefox',
+
+    # Chromium Images
+    'NodeChromium': 'node-chromium',
+    'StandaloneChromium': 'standalone-chromium',
 }
 
 TEST_NAME_MAP = {
@@ -56,6 +60,10 @@
     'NodeFirefox': 'FirefoxTests',
     'StandaloneFirefox': 'FirefoxTests',
 
+    # Chromium Images
+    'NodeChromium': 'ChromeTests',
+    'StandaloneChromium': 'ChromeTests',
+
     # Chart Parallel Test
     'ParallelAutoscaling': 'ParallelAutoscalingTests'
 }
@@ -165,7 +173,7 @@ def get_build_path(container):
 
 
 def standalone_browser_container_matches(container):
-    return re.match("(Standalone)(Chrome|Firefox|Edge)", container)
+    return re.match("(Standalone)(Chrome|Firefox|Edge|Chromium)", container)
 
 
 if __name__ == '__main__':