name: 'Build Docker' description: 'Build Docker images for Rocket.Chat and related services' inputs: CR_USER: required: true description: 'GitHub Container Registry username' CR_PAT: required: true description: 'GitHub Container Registry Personal Access Token' DOCKER_USER: required: false description: 'DockerHub username (required for FIPS builds to pull private base image)' DOCKER_PASS: required: false description: 'DockerHub password (required for FIPS builds to pull private base image)' deno-version: required: true description: 'Deno version' arch: required: false description: 'Architecture' default: 'arm64' service: required: false description: 'Container to build' publish-image: required: false description: 'Publish image' default: 'true' setup-docker: required: false description: 'Setup Docker' default: 'true' type: required: false description: 'production, coverage, or fips' default: 'coverage' runs: using: composite steps: - name: Login to GitHub Container Registry if: inputs.publish-image == 'true' && github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ inputs.CR_USER }} password: ${{ inputs.CR_PAT }} - name: Login to DockerHub # FIPS base image (rocketchat/dhi-node) lives in a private DockerHub repo and requires auth to pull. if: inputs.type == 'fips' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.event_name == 'merge_group' || github.ref == 'refs/heads/develop') uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ inputs.DOCKER_USER }} password: ${{ inputs.DOCKER_PASS }} - name: Restore meteor build if: inputs.service == 'rocketchat' uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: build-${{ inputs.type == 'fips' && ((github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production') || inputs.type }} path: /tmp/build - name: Unpack meteor build if: inputs.service == 'rocketchat' shell: bash run: | cd /tmp/build tar xzf Rocket.Chat.tar.gz rm Rocket.Chat.tar.gz - name: Set up Docker if: inputs.setup-docker == 'true' uses: docker/setup-docker-action@e43656e248c0bd0647d3f5c195d116aacf6fcaf4 # v4.7.0 with: daemon-config: | { "debug": false, "features": { "containerd-snapshotter": true } } # Microservice images build from a pruned, self-contained context (out/) # produced by `turbo prune`, instead of a hand-maintained list of COPY lines. # The rocketchat (Meteor) image is a prebuilt bundle and is staged separately above. - name: Stage service build context if: inputs.service != 'rocketchat' shell: bash env: INPUT_SERVICE: ${{ inputs.service }} run: | set -o xtrace # The Docker SERVICE arg (folder under ee/apps) lives in the compose build args; # the compose service name (INPUT_SERVICE) can differ (e.g. ddp-streamer-service). SERVICE_DIR=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].build.args.SERVICE') PKG_NAME=$(node -p "require('./ee/apps/${SERVICE_DIR}/package.json').name") TURBO_VERSION=$(node -p "require('./package.json').devDependencies.turbo") rm -rf "out/${SERVICE_DIR}" # `turbo prune` derives the exact workspace dependency closure for the service — no # hand-maintained list. --use-gitignore=false also captures the gitignored build output # we need (dist/, generated definition/), at the cost of dragging in node_modules/.turbo, # which we strip below; `yarn workspaces focus --production` rebuilds node_modules in-image. npx -y "turbo@${TURBO_VERSION}" prune "$PKG_NAME" --skip-infer --use-gitignore=false --out-dir "out/${SERVICE_DIR}" find "out/${SERVICE_DIR}" \( -name node_modules -o -name .turbo \) -type d -prune -exec rm -rf {} + - name: Build Docker images shell: bash env: DENO_VERSION: ${{ inputs.deno-version }} INPUT_ARCH: ${{ inputs.arch }} INPUT_SERVICE: ${{ inputs.service }} INPUT_PUBLISH_IMAGE: ${{ inputs.publish-image }} INPUT_TYPE: ${{ inputs.type }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_EVENT_NAME: ${{ github.event_name }} GITHUB_REF: ${{ github.ref }} run: | set -o xtrace compose_fips_override='' if [[ "$INPUT_TYPE" == 'fips' ]]; then compose_fips_override='-f docker-compose-ci.fips.yml' fi # Removes unnecessary swc cores and sharp binaries to reduce image size swc_arch='x64' if [[ "$INPUT_SERVICE" == 'rocketchat' ]]; then if [[ "$INPUT_ARCH" == 'arm64' ]]; then swc_arch='arm64' fi find /tmp/build/bundle/programs/server/npm/node_modules/@img -type d -name 'sharp-*' -not -name "*-linuxmusl-${swc_arch}" -exec rm -rf {} + find /tmp/build/bundle/programs/server/npm/node_modules/@napi-rs -type d -name 'pinyin-linux-*' -not -name "*-linux-${swc_arch}-*" -exec rm -rf {} + find /tmp/build/bundle/programs/server/npm/node_modules/@esbuild -type d -name 'linux-*' -not -name "*-${swc_arch}" -exec rm -rf {} + find /tmp/build/bundle/programs/server/npm/node_modules/@rocket.chat/apps/node_modules/@esbuild -type d -name 'linux-*' -not -name "*-${swc_arch}" -exec rm -rf {} + fi if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then LOAD_OR_PUSH="--push" else LOAD_OR_PUSH="--load" fi # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image') docker buildx bake \ -f docker-compose-ci.yml $compose_fips_override \ ${LOAD_OR_PUSH} \ --allow=fs.read=/tmp/build \ --set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \ --set "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ --set "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ --set "*.platform=linux/${INPUT_ARCH}" \ --set *.cache-from=type=gha \ --set *.cache-to=type=gha,mode=max \ --provenance=false \ --sbom=false \ --metadata-file "/tmp/meta.json" \ "$INPUT_SERVICE" echo "Contents of /tmp/meta.json:" cat /tmp/meta.json if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then SERVICE_SUFFIX='' if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then SERVICE_SUFFIX='-cov' elif [[ "$INPUT_TYPE" == 'fips' ]]; then SERVICE_SUFFIX='-fips' fi mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}" # Get digest and image info DIGEST=$(jq -r --arg service "$INPUT_SERVICE" '.[$service].["containerimage.digest"]' "/tmp/meta.json") IMAGE_NO_TAG=$(echo "$IMAGE" | sed 's/:.*$//') FULL_IMAGE="${IMAGE_NO_TAG}@${DIGEST}" echo "Inspecting image: $FULL_IMAGE" # Inspect the image and save complete manifest with sizes (using -v for verbose) docker manifest inspect -v "$FULL_IMAGE" > "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json" echo "Saved manifest to /tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json" cat "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}/manifest.json" | jq '.' fi - name: Save Docker image as artifact if: inputs.publish-image == 'false' && inputs.arch == 'amd64' shell: bash env: SERVICE: ${{ inputs.service }} ARCH: ${{ inputs.arch }} TYPE: ${{ inputs.type }} run: | set -o xtrace compose_fips_override='' if [[ "$TYPE" == 'fips' ]]; then compose_fips_override='-f docker-compose-ci.fips.yml' fi # Get image name from docker-compose-ci.yml IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image') # Create directory for image archives mkdir -p /tmp/docker-images # Save the image to a tar file docker save "${IMAGE}" -o "/tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" echo "Saved image to /tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar" ls -lh /tmp/docker-images/ - name: Upload Docker image artifact if: inputs.publish-image == 'false' && inputs.arch == 'amd64' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: docker-image-${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }} path: /tmp/docker-images/${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }}.tar retention-days: 1 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: inputs.publish-image == 'true' with: name: manifests-${{ inputs.service }}-${{ inputs.arch }}-${{ inputs.type }} path: /tmp/manifests retention-days: 5 - name: Clean up temporary files if: inputs.service == 'rocketchat' shell: bash run: | sudo rm -rf /tmp/build