--- # Github Actions release for rclone # -*- compile-command: "yamllint -f parsable build_publish_docker_image.yml" -*- name: Build & Push Docker Images # Trigger the workflow on push or pull request on: push: branches: - '**' tags: - '**' workflow_dispatch: inputs: manual: description: Manual run (bypass default conditions) type: boolean default: true jobs: build-image: if: inputs.manual || (github.repository == 'rclone/rclone' && github.event_name != 'pull_request') timeout-minutes: 60 strategy: fail-fast: false matrix: include: - platform: linux/amd64 runs-on: ubuntu-24.04 - platform: linux/386 runs-on: ubuntu-24.04 - platform: linux/arm64 runs-on: ubuntu-24.04-arm - platform: linux/arm/v7 runs-on: ubuntu-24.04-arm - platform: linux/arm/v6 runs-on: ubuntu-24.04-arm name: Build Docker Image for ${{ matrix.platform }} runs-on: ${{ matrix.runs-on }} steps: - name: Free Space shell: bash run: | df -h . # Remove android SDK sudo rm -rf /usr/local/lib/android || true # Remove .net runtime sudo rm -rf /usr/share/dotnet || true df -h . - name: Checkout Repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set REPO_NAME Variable run: | echo "REPO_NAME=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} - name: Set PLATFORM Variable run: | platform=${{ matrix.platform }} echo "PLATFORM=${platform//\//-}" >> $GITHUB_ENV - name: Set CACHE_NAME Variable shell: python run: | import os, re def slugify(input_string, max_length=63): slug = input_string.lower() slug = re.sub(r'[^a-z0-9 -]', ' ', slug) slug = slug.strip() slug = re.sub(r'\s+', '-', slug) slug = re.sub(r'-+', '-', slug) slug = slug[:max_length] slug = re.sub(r'[-]+$', '', slug) return slug ref_name_slug = "cache" if os.environ.get("GITHUB_REF_NAME") and os.environ['GITHUB_EVENT_NAME'] == "pull_request": ref_name_slug += "-pr-" + slugify(os.environ['GITHUB_REF_NAME']) with open(os.environ['GITHUB_ENV'], 'a') as env: env.write(f"CACHE_NAME={ref_name_slug}\n") - name: Get ImageOS # There's no way around this, because "ImageOS" is only available to # processes, but the setup-go action uses it in its key. id: imageos uses: actions/github-script@v7 with: result-encoding: string script: | return process.env.ImageOS - name: Extract Metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 env: DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,manifest-descriptor # Important for digest annotation (used by Github packages) with: images: | ghcr.io/${{ env.REPO_NAME }} labels: | org.opencontainers.image.url=https://github.com/rclone/rclone/pkgs/container/rclone org.opencontainers.image.vendor=${{ github.repository_owner }} org.opencontainers.image.authors=rclone org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} tags: | type=sha type=ref,event=pr type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} type=raw,value=beta,enable={{is_default_branch}} - name: Setup QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Load Go Build Cache for Docker id: go-cache uses: actions/cache@v4 with: key: ${{ runner.os }}-${{ steps.imageos.outputs.result }}-go-${{ env.CACHE_NAME }}-${{ env.PLATFORM }}-${{ hashFiles('**/go.mod') }}-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ steps.imageos.outputs.result }}-go-${{ env.CACHE_NAME }}-${{ env.PLATFORM }} # Cache only the go builds, the module download is cached via the docker layer caching path: | go-build-cache - name: Inject Go Build Cache into Docker uses: reproducible-containers/buildkit-cache-dance@v3 with: cache-map: | { "go-build-cache": "/root/.cache/go-build" } skip-extraction: ${{ steps.go-cache.outputs.cache-hit }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io # This is the user that triggered the Workflow. In this case, it will # either be the user whom created the Release or manually triggered # the workflow_dispatch. username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and Publish Image Digest id: build uses: docker/build-push-action@v6 with: file: Dockerfile context: . provenance: false # don't specify 'tags' here (error "get can't push tagged ref by digest") # tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta.outputs.annotations }} platforms: ${{ matrix.platform }} outputs: | type=image,name=ghcr.io/${{ env.REPO_NAME }},push-by-digest=true,name-canonical=true,push=true cache-from: | type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:build-${{ env.CACHE_NAME }}-${{ env.PLATFORM }} cache-to: | type=registry,ref=ghcr.io/${{ env.REPO_NAME }}:build-${{ env.CACHE_NAME }}-${{ env.PLATFORM }},image-manifest=true,mode=max,compression=zstd - name: Export Image Digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload Image Digest uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM }} path: /tmp/digests/* retention-days: 1 if-no-files-found: error merge-image: name: Merge & Push Final Docker Image runs-on: ubuntu-24.04 needs: - build-image steps: - name: Download Image Digests uses: actions/download-artifact@v4 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Set REPO_NAME Variable run: | echo "REPO_NAME=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} - name: Extract Metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 env: DOCKER_METADATA_ANNOTATIONS_LEVELS: index with: images: | ${{ env.REPO_NAME }} ghcr.io/${{ env.REPO_NAME }} labels: | org.opencontainers.image.url=https://github.com/rclone/rclone/pkgs/container/rclone org.opencontainers.image.vendor=${{ github.repository_owner }} org.opencontainers.image.authors=rclone org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} tags: | type=sha type=ref,event=pr type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} type=raw,value=beta,enable={{is_default_branch}} - name: Extract Tags shell: python run: | import json, os metadata_json = os.environ['DOCKER_METADATA_OUTPUT_JSON'] metadata = json.loads(metadata_json) tags = [f"--tag '{tag}'" for tag in metadata["tags"]] tags_string = " ".join(tags) with open(os.environ['GITHUB_ENV'], 'a') as env: env.write(f"TAGS={tags_string}\n") - name: Extract Annotations shell: python run: | import json, os metadata_json = os.environ['DOCKER_METADATA_OUTPUT_JSON'] metadata = json.loads(metadata_json) annotations = [f"--annotation '{annotation}'" for annotation in metadata["annotations"]] annotations_string = " ".join(annotations) with open(os.environ['GITHUB_ENV'], 'a') as env: env.write(f"ANNOTATIONS={annotations_string}\n") - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io # This is the user that triggered the Workflow. In this case, it will # either be the user whom created the Release or manually triggered # the workflow_dispatch. username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Create & Push Manifest List working-directory: /tmp/digests run: | docker buildx imagetools create \ ${{ env.TAGS }} \ ${{ env.ANNOTATIONS }} \ $(printf 'ghcr.io/${{ env.REPO_NAME }}@sha256:%s ' *) - name: Inspect and Run Multi-Platform Image run: | docker buildx imagetools inspect --raw ${{ env.REPO_NAME }}:${{ steps.meta.outputs.version }} docker buildx imagetools inspect --raw ghcr.io/${{ env.REPO_NAME }}:${{ steps.meta.outputs.version }} docker run --rm ghcr.io/${{ env.REPO_NAME }}:${{ steps.meta.outputs.version }} version