name: "Docker build & push" on: workflow_dispatch: inputs: sha: description: 'The commit SHA to build' type: string required: true branch_name: description: 'The branch name to build' type: string required: true workflow_call: inputs: sha: required: true type: string branch_name: required: true type: string outputs: image_version: description: "Docker branch tag" value: ${{ jobs.calculate_version.outputs.image_version }} build_version: description: "Docker version tag" value: ${{ jobs.calculate_version.outputs.build_version }} env: DOCKER_CLI_EXPERIMENTAL: enabled IMAGE_NAME: "${{ vars.DOCKERHUB_ORG }}/label-studio" DOCKER_IMAGE_TAG_CHECK_NAME: "Docker image tag" POETRY_VERSION: 2.1.4 jobs: calculate_version: name: "Calculate version" runs-on: ubuntu-latest outputs: image_version: ${{ steps.version.outputs.image_version }} build_version: ${{ steps.version.outputs.build_version }} pretty_branch_name: ${{ steps.version.outputs.pretty_branch_name }} sha: ${{ steps.version.outputs.sha }} steps: - name: Checkout uses: actions/checkout@v6 with: submodules: 'recursive' ref: ${{ inputs.sha }} fetch-depth: 1 - name: Calculate version id: version env: BRANCH_NAME: ${{ inputs.branch_name }} run: | set -x sha="$(git rev-parse HEAD)" echo "sha=$sha" >> $GITHUB_OUTPUT pretty_branch_name="$(echo -n "${BRANCH_NAME#refs/heads/}" | sed -E 's#[/_\.-]+#-#g' | tr '[:upper:]' '[:lower:]' | cut -c1-25 | sed -E 's#-$##g')" echo "pretty_branch_name=${pretty_branch_name}" >> "${GITHUB_OUTPUT}" regexp='^ls-release\/(.*)$'; if [[ "$BRANCH_NAME" =~ $regexp ]]; then image_version="${BASH_REMATCH[1]}rc${sha}" else image_version="${pretty_branch_name}" fi echo "image_version=${image_version}" >> $GITHUB_OUTPUT current_time="$(date +'%Y%m%d.%H%M%S')" branch="-${pretty_branch_name}" short_sha="$(git rev-parse --short HEAD)" long_sha="$(git rev-parse HEAD)" echo "sha=$long_sha" >> $GITHUB_OUTPUT short_sha_length="$(echo $short_sha | awk '{print length}')" current_time_length="$(echo $current_time | awk '{print length}')" version="${current_time}$(echo $branch | cut -c1-$((50 - short_sha_length - current_time_length)))-${short_sha}" echo "build_version=$version" >> $GITHUB_OUTPUT docker_build: name: "Docker image (${{ matrix.platform }})" timeout-minutes: 90 runs-on: ${{ matrix.runner }} needs: calculate_version strategy: fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - uses: hmarr/debug-action@v3.0.0 - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v6 with: submodules: 'recursive' ref: ${{ inputs.sha }} fetch-depth: 2147483647 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.11.1 - name: Login to DockerHub uses: docker/login-action@v3.5.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Download LaunchDarkly Community config env: LAUNCHDARKLY_COMMUNITY_SDK_KEY: ${{ secrets.LAUNCHDARKLY_COMMUNITY_SDK_KEY }} LAUNCHDARKLY_DOWNLOAD_PATH: "label_studio/feature_flags.json" run: | set -xeuo pipefail curl \ --connect-timeout 30 \ --retry 5 \ --retry-delay 10 \ -H "Authorization: $LAUNCHDARKLY_COMMUNITY_SDK_KEY" \ "https://sdk.launchdarkly.com/sdk/latest-all" >"$LAUNCHDARKLY_DOWNLOAD_PATH" if [ "$(jq 'has("flags")' <<< cat $LAUNCHDARKLY_DOWNLOAD_PATH)" = "true" ]; then echo "feature_flags.json is valid" else echo "feature_flags.json is invalid" cat $LAUNCHDARKLY_DOWNLOAD_PATH exit 1 fi - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME }} - name: Push Docker image (${{ matrix.platform }}) uses: docker/build-push-action@v6.18.0 id: docker_build_and_push with: context: . file: Dockerfile platforms: ${{ matrix.platform }} sbom: true provenance: true tags: ${{ env.IMAGE_NAME }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=min outputs: type=image,push-by-digest=true,name-canonical=true,push=true build-args: | BRANCH_OVERRIDE=${{ inputs.branch_name }} VERSION_OVERRIDE=${{ needs.calculate_version.outputs.build_version }} POETRY_VERSION=${{ env.POETRY_VERSION }} - name: Export digest run: | mkdir -p ${{ runner.temp }}/digests digest="${{ steps.docker_build_and_push.outputs.digest }}" touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v6 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* if-no-files-found: error retention-days: 1 merge_docker_manifest: runs-on: ubuntu-latest needs: - docker_build - calculate_version steps: - name: Download digests uses: actions/download-artifact@v7 with: path: ${{ runner.temp }}/digests pattern: digests-* merge-multiple: true - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME }} tags: | type=raw,value=${{ needs.calculate_version.outputs.image_version }} type=raw,value=${{ needs.calculate_version.outputs.build_version }} - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) - name: Inspect image run: | docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} create_check: name: "Create Docker image tag Check" runs-on: ubuntu-latest needs: [calculate_version, docker_build, merge_docker_manifest] if: success() steps: - name: Create Docker image tag Check uses: actions/github-script@v8 with: script: | const { repo, owner } = context.repo; const details = { "branch": "${{ inputs.branch_name }}", "pretty_branch_name": "${{ needs.calculate_version.outputs.pretty_branch_name }}", "image_version": "${{ needs.calculate_version.outputs.image_version }}", "sha": "${{ needs.calculate_version.outputs.sha }}" } const { data: check } = await github.rest.checks.create({ owner, repo, name: '${{ env.DOCKER_IMAGE_TAG_CHECK_NAME }}', head_sha: '${{ needs.calculate_version.outputs.sha }}', status: 'in_progress', output: { title: '${{ env.DOCKER_IMAGE_TAG_CHECK_NAME }}', summary: JSON.stringify(details) } }); await github.rest.checks.update({ owner, repo, check_run_id: check.id, status: 'completed', conclusion: 'success' }); notify_slack: name: "Notify Slack" runs-on: ubuntu-latest needs: [calculate_version, docker_build, create_check, merge_docker_manifest] if: always() && github.event_name == 'push' && inputs.branch_name == 'develop' steps: - name: Notify to Slack on failure if: failure() env: GITHUB_REPOSITORY: "${{ github.repository }}" GITHUB_RUN_ID: "${{ github.run_id }}" GITHUB_SHA: "${{ github.sha }}" SLACK_BOT_TOKEN: ${{ secrets.SLACK_LSE_BOT_TOKEN }} uses: slackapi/slack-github-action@v1.27 with: channel-id: 'C01RJV08UJK' slack-message: | ❌ Docker build failed for *develop* branch! >