name: Docker on: workflow_dispatch: push: branches: [main] pull_request: branches: [main] release: types: [published] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: packages: write jobs: pre-job: runs-on: ubuntu-latest outputs: should_run_server: ${{ steps.found_paths.outputs.server == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }} steps: - name: Checkout code uses: actions/checkout@v4 - id: found_paths uses: dorny/paths-filter@v3 with: filters: | server: - 'server/**' - 'openapi/**' - 'web/**' - 'i18n/**' machine-learning: - 'machine-learning/**' workflow: - '.github/workflows/docker.yml' - name: Check if we should force jobs to run id: should_force run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' }}" >> "$GITHUB_OUTPUT" retag_ml: name: Re-Tag ML needs: pre-job if: ${{ needs.pre-job.outputs.should_run_ml == 'false' && !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest strategy: matrix: suffix: ["", "-cuda", "-openvino", "-armnn"] steps: - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Re-tag image run: | REGISTRY_NAME="ghcr.io" REPOSITORY=${{ github.repository_owner }}/immich-machine-learning TAG_OLD=main${{ matrix.suffix }} TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD retag_server: name: Re-Tag Server needs: pre-job if: ${{ needs.pre-job.outputs.should_run_server == 'false' && !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest strategy: matrix: suffix: [""] steps: - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Re-tag image run: | REGISTRY_NAME="ghcr.io" REPOSITORY=${{ github.repository_owner }}/immich-server TAG_OLD=main${{ matrix.suffix }} TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD build_and_push_ml: name: Build and Push ML needs: pre-job if: ${{ needs.pre-job.outputs.should_run_ml == 'true' }} runs-on: ${{ matrix.runner }} env: image: immich-machine-learning context: machine-learning file: machine-learning/Dockerfile GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning strategy: # Prevent a failure in one image from stopping the other builds fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest device: cpu - platform: linux/arm64 runner: ubuntu-24.04-arm device: cpu - platform: linux/amd64 runner: ubuntu-latest device: cuda suffix: -cuda - platform: linux/amd64 runner: ubuntu-latest device: openvino suffix: -openvino - platform: linux/arm64 runner: ubuntu-24.04-arm device: armnn suffix: -armnn steps: - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.9.0 - name: Login to GitHub Container Registry uses: docker/login-action@v3 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Generate cache key suffix run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV else echo "CACHE_KEY_SUFFIX=$(echo ${{ github.ref_name }} | sed 's/[^a-zA-Z0-9]/-/g')" >> $GITHUB_ENV fi - name: Generate cache target id: cache-target run: | if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then # Essentially just ignore the cache output (forks can't write to registry cache) echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT else echo "cache-to=type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }},mode=max,compression=zstd" >> $GITHUB_OUTPUT fi - name: Build and push image id: build uses: docker/build-push-action@v6.13.0 with: context: ${{ env.context }} file: ${{ env.file }} platforms: ${{ matrix.platforms }} labels: ${{ steps.metadata.outputs.labels }} cache-to: ${{ steps.cache-target.outputs.cache-to }} cache-from: | type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }} type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-main outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }} build-args: | DEVICE=${{ matrix.device }} BUILD_ID=${{ github.run_id }} BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }} BUILD_SOURCE_REF=${{ github.ref_name }} BUILD_SOURCE_COMMIT=${{ github.sha }} - name: Export digest run: | mkdir -p ${{ runner.temp }}/digests digest="${{ steps.build.outputs.digest }}" touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* if-no-files-found: error retention-days: 1 merge_ml: name: Merge & Push ML runs-on: ubuntu-latest if: ${{ needs.pre-job.outputs.should_run_ml == 'true' && !github.event.pull_request.head.repo.fork }} env: GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning DOCKER_REPO: altran1502/immich-machine-learning strategy: matrix: include: - device: cpu - device: cuda suffix: -cuda - device: openvino suffix: -openvino - device: armnn suffix: -armnn needs: - build_and_push_ml steps: - name: Download digests uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: ml-digests-${{ matrix.device }}-* merge-multiple: true - name: Login to Docker Hub if: ${{ github.event_name == 'release' }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate docker image tags id: meta uses: docker/metadata-action@v5 env: DOCKER_METADATA_PR_HEAD_SHA: "true" with: flavor: | # Disable latest tag latest=false images: | name=${{ env.GHCR_REPO }} name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }} tags: | # Tag with branch name type=ref,event=branch,suffix=${{ matrix.suffix }} # Tag with pr-number type=ref,event=pr,suffix=${{ matrix.suffix }} # Tag with long commit sha hash type=sha,format=long,prefix=commit-,suffix=${{ matrix.suffix }} # Tag with git tag on release type=ref,event=tag,suffix=${{ matrix.suffix }} type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }} - 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.GHCR_REPO }}@sha256:%s ' *) build_and_push_server: name: Build and Push Server runs-on: ${{ matrix.runner }} needs: pre-job if: ${{ needs.pre-job.outputs.should_run_server == 'true' }} env: image: immich-server context: . file: server/Dockerfile GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server strategy: fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Generate cache key suffix run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV else echo "CACHE_KEY_SUFFIX=$(echo ${{ github.ref_name }} | sed 's/[^a-zA-Z0-9]/-/g')" >> $GITHUB_ENV fi - name: Generate cache target id: cache-target run: | if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then # Essentially just ignore the cache output (forks can't write to registry cache) echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT else echo "cache-to=type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }},mode=max,compression=zstd" >> $GITHUB_OUTPUT fi - name: Build and push image id: build uses: docker/build-push-action@v6.13.0 with: context: ${{ env.context }} file: ${{ env.file }} platforms: ${{ matrix.platform }} labels: ${{ steps.metadata.outputs.labels }} cache-to: ${{ steps.cache-target.outputs.cache-to }} cache-from: | type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ env.CACHE_KEY_SUFFIX }} type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-main outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }} build-args: | DEVICE=cpu BUILD_ID=${{ github.run_id }} BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }} BUILD_SOURCE_REF=${{ github.ref_name }} BUILD_SOURCE_COMMIT=${{ github.sha }} - name: Export digest run: | mkdir -p ${{ runner.temp }}/digests digest="${{ steps.build.outputs.digest }}" touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: server-digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* if-no-files-found: error retention-days: 1 merge_server: name: Merge & Push Server runs-on: ubuntu-latest if: ${{ needs.pre-job.outputs.should_run_server == 'true' && !github.event.pull_request.head.repo.fork }} env: GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server DOCKER_REPO: altran1502/immich-server needs: - build_and_push_server steps: - name: Download digests uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: server-digests-* merge-multiple: true - name: Login to Docker Hub if: ${{ github.event_name == 'release' }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate docker image tags id: meta uses: docker/metadata-action@v5 env: DOCKER_METADATA_PR_HEAD_SHA: "true" with: flavor: | # Disable latest tag latest=false images: | name=${{ env.GHCR_REPO }} name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }} tags: | # Tag with branch name type=ref,event=branch,suffix=${{ matrix.suffix }} # Tag with pr-number type=ref,event=pr,suffix=${{ matrix.suffix }} # Tag with long commit sha hash type=sha,format=long,prefix=commit-,suffix=${{ matrix.suffix }} # Tag with git tag on release type=ref,event=tag,suffix=${{ matrix.suffix }} type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }} - 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.GHCR_REPO }}@sha256:%s ' *) success-check-server: name: Docker Build & Push Server Success needs: [merge_server, retag_server] runs-on: ubuntu-latest if: always() steps: - name: Any jobs failed? if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 - name: All jobs passed or skipped if: ${{ !(contains(needs.*.result, 'failure')) }} run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}" success-check-ml: name: Docker Build & Push ML Success needs: [merge_ml, retag_ml] runs-on: ubuntu-latest if: always() steps: - name: Any jobs failed? if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 - name: All jobs passed or skipped if: ${{ !(contains(needs.*.result, 'failure')) }} run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}"