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:
- Check the Immich releases page for the latest version
- Edit
immich/.envlocally — changeIMMICH_VERSION=v2.5.0toIMMICH_VERSION=v2.6.0 - Commit and push
- SSH into the server
- Pull the changes, run
docker compose down && docker compose up -d - 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:
- Select the service — dropdown with the supported services
- Enter the version — for Immich, it must match
v{major}.{minor}.{patch}format
The workflow then:
-
Validates the version format — rejects anything that doesn’t match the expected pattern. No accidental
2.6.3without thevprefix, no typos likev2.6missing the patch version. -
Updates the config file — for Immich, it modifies
IMMICH_VERSIONinimmich/.envusingsed. -
Commits and pushes — the bot commits the change to
mainwith a clean message likeupdate 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.