Skip to main content
PCSalt
YouTube GitHub
Back to Homelab
Homelab · 3 min read

Automating Immich Version Updates with GitHub Actions

A GitHub Actions workflow to update Immich versions with validation, auto-commit, and chained deployment — no SSH required.


Immich is a self-hosted photo and video management platform — think Google Photos, but running on your own hardware. It supports ML-powered search, face recognition, and automatic organization. I’ve been running it on my home server for a while and it’s become the go-to for our family photos.

The one thing that used to be annoying? Updating it.

The Manual Update Process

Immich uses a version-pinned Docker image. The version is defined in an .env file:

IMMICH_VERSION=v2.5.0

And the docker-compose.yml references it:

services:
  immich-server:
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}

To update, I had to:

  1. Check the Immich releases page for the latest version
  2. Edit immich/.env locally — change IMMICH_VERSION=v2.5.0 to IMMICH_VERSION=v2.6.0
  3. Commit and push
  4. SSH into the server
  5. Pull the changes, run docker compose down && docker compose up -d
  6. Verify everything came up cleanly

Steps 3-6 are already handled by the deployment automation I set up. But steps 1-2 still required me to manually edit a file and push a commit. I wanted a single-click version update.

The Workflow

Here’s the update-version.yml workflow that handles it:

name: Update Service Version

on:
  workflow_dispatch:
    inputs:
      service:
        description: 'Service to update'
        required: true
        type: choice
        options:
          - immich
          - nextcloud
      version:
        description: 'New version (e.g., v2.3.0 for immich)'
        required: true
        type: string

permissions:
  contents: write

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.PAT_TOKEN }}

      - name: Validate version
        run: |
          VERSION="${{ inputs.version }}"
          case "${{ inputs.service }}" in
            immich)
              if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                echo "::error::Invalid immich version '$VERSION'. \
                  Must match format v{major}.{minor}.{patch} (e.g., v2.6.3)"
                exit 1
              fi
              ;;
          esac

      - name: Update version
        run: |
          case "${{ inputs.service }}" in
            immich)
              sed -i "s/^IMMICH_VERSION=.*/IMMICH_VERSION=${{ inputs.version }}/" \
                immich/.env
              echo "Updated immich to ${{ inputs.version }}"
              cat immich/.env
              ;;
          esac

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email \
            "github-actions[bot]@users.noreply.github.com"
          git add -A
          git commit -m "update ${{ inputs.service }} \
            to ${{ inputs.version }}"
          git push

How It Works

The workflow is triggered manually via workflow_dispatch. From the GitHub Actions UI, I pick the service and type the new version:

  1. Select the service — dropdown with the supported services
  2. Enter the version — for Immich, it must match v{major}.{minor}.{patch} format

The workflow then:

  1. Validates the version format — rejects anything that doesn’t match the expected pattern. No accidental 2.6.3 without the v prefix, no typos like v2.6 missing the patch version.

  2. Updates the config file — for Immich, it modifies IMMICH_VERSION in immich/.env using sed.

  3. Commits and pushes — the bot commits the change to main with a clean message like update immich to v2.6.3.

The Chain Reaction

Here’s where it gets good. The commit pushed by this workflow modifies a file inside immich/. That triggers the existing deploy workflow because of the path-based trigger:

# deploy-immich.yml
on:
  push:
    branches: [main]
    paths: ['immich/**']

So the full chain is:

Manual trigger (version + service)
  → Validate version format
  → Update .env file
  → Commit and push to main
  → Path trigger detects immich/** change
  → Deploy workflow runs
  → Immich restarts with new version

One click in the GitHub UI. No SSH. No manual file edits. No forgetting to restart.

Why a PAT Token?

You might have noticed the checkout step uses secrets.PAT_TOKEN instead of the default GITHUB_TOKEN:

- uses: actions/checkout@v4
  with:
    token: ${{ secrets.PAT_TOKEN }}

This is intentional. Commits made with the default GITHUB_TOKEN don’t trigger other workflows. Since the whole point is to chain the version update into a deployment, the commit needs to be made with a Personal Access Token that has permission to trigger workflows.

Version Validation

The validation step is a small but important safety net. Immich expects versions like v2.6.3 — if you accidentally type 2.6.3 or v2.6, the workflow fails immediately with a clear error message instead of committing a broken config:

::error::Invalid immich version '2.6.3'. Must match format
  v{major}.{minor}.{patch} (e.g., v2.6.3)

This catches typos before they reach the server. Better to fail in CI than to find out Immich didn’t start because Docker couldn’t find the image tag.

Extending to Other Services

The workflow already supports Nextcloud alongside Immich. The pattern is the same — validate the version format, update the right file, commit and push. For Nextcloud, the version goes into the Dockerfile instead of an .env file:

sed -i "s|^FROM nextcloud:.*|FROM nextcloud:${{ inputs.version }}|" \
  nextcloud/Dockerfile

Adding another service is just a new case block in the validate and update steps, and a new option in the workflow_dispatch dropdown.

Wrapping Up

This workflow turned a multi-step manual process into a single-click operation. The version validation prevents typos, the auto-commit keeps the repo as the source of truth, and the chained deployment means the server is always running what main says it should.

Combined with the path-based deployment setup, updating Immich now takes about 30 seconds — open GitHub Actions, pick the version, click run. Done.