From c4fd23855ba079dc500aafa8eda39e69741a78e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 13 Jan 2026 17:23:25 +0100 Subject: [PATCH] feat(media): add Immich photo/video management service - Add immich_server, immich_ml, and immich_postgres services - Use dedicated PostgreSQL with vector extensions (vectorchord + pgvectors) - Connect to core Redis for job queues - Configure Traefik routing for immich.media.pivoine.art - Add backup volumes and plan for Backrest (daily at 12:00) Co-Authored-By: Claude Opus 4.5 --- arty.yml | 7 ++++ core/backrest/config.json | 19 +++++++++ core/compose.yaml | 8 ++++ media/compose.yaml | 85 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) diff --git a/arty.yml b/arty.yml index 29b4cea..a5d2adc 100644 --- a/arty.yml +++ b/arty.yml @@ -85,6 +85,13 @@ envs: MEDIA_JELLYFIN_TRAEFIK_HOST: jellyfin.media.pivoine.art MEDIA_PINCHFLAT_IMAGE: ghcr.io/kieraneglin/pinchflat:latest MEDIA_PINCHFLAT_TRAEFIK_HOST: pinchflat.media.pivoine.art + # Immich - Photo and video management + MEDIA_IMMICH_SERVER_IMAGE: ghcr.io/immich-app/immich-server:release + MEDIA_IMMICH_ML_IMAGE: ghcr.io/immich-app/immich-machine-learning:release + MEDIA_IMMICH_POSTGRES_IMAGE: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 + MEDIA_IMMICH_TRAEFIK_HOST: immich.media.pivoine.art + MEDIA_IMMICH_DB_NAME: immich + MEDIA_IMMICH_DB_USER: immich # Dev (Gitea + Coolify) DEV_TRAEFIK_ENABLED: true DEV_COMPOSE_PROJECT_NAME: dev diff --git a/core/backrest/config.json b/core/backrest/config.json index 840b3c2..0a7b399 100644 --- a/core/backrest/config.json +++ b/core/backrest/config.json @@ -346,6 +346,25 @@ "yearly": 3 } } + }, + { + "id": "immich-backup", + "repo": "hidrive-backup", + "paths": [ + "/volumes/immich_postgres_data", + "/volumes/immich_upload" + ], + "schedule": { + "cron": "0 12 * * *" + }, + "retention": { + "policyTimeBucketed": { + "daily": 7, + "weekly": 4, + "monthly": 6, + "yearly": 2 + } + } } ] } diff --git a/core/compose.yaml b/core/compose.yaml index 946e229..b7c0bec 100644 --- a/core/compose.yaml +++ b/core/compose.yaml @@ -88,6 +88,8 @@ services: - backup_dev_gitea_config:/volumes/dev_gitea_config:ro - backup_dev_gitea_runner_data:/volumes/dev_gitea_runner_data:ro - backup_dev_coolify_data:/volumes/dev_coolify_data:ro + - backup_media_immich_postgres_data:/volumes/immich_postgres_data:ro + - backup_media_immich_upload:/volumes/immich_upload:ro environment: TZ: ${TIMEZONE:-Europe/Berlin} @@ -200,3 +202,9 @@ volumes: backup_dev_coolify_data: name: dev_coolify_data external: true + backup_media_immich_postgres_data: + name: media_immich_postgres_data + external: true + backup_media_immich_upload: + name: media_immich_upload + external: true diff --git a/media/compose.yaml b/media/compose.yaml index 09aa548..6814999 100644 --- a/media/compose.yaml +++ b/media/compose.yaml @@ -65,6 +65,85 @@ services: # Watchtower - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + # Immich PostgreSQL - Dedicated database with vector extensions + immich_postgres: + image: ${MEDIA_IMMICH_POSTGRES_IMAGE:-ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + POSTGRES_USER: ${MEDIA_IMMICH_DB_USER:-immich} + POSTGRES_PASSWORD: ${MEDIA_IMMICH_DB_PASSWORD} + POSTGRES_DB: ${MEDIA_IMMICH_DB_NAME:-immich} + POSTGRES_INITDB_ARGS: --data-checksums + volumes: + - immich_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${MEDIA_IMMICH_DB_USER:-immich} -d ${MEDIA_IMMICH_DB_NAME:-immich}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - compose_network + + # Immich Server - Main application + immich_server: + image: ${MEDIA_IMMICH_SERVER_IMAGE:-ghcr.io/immich-app/immich-server:release} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_server + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + DB_HOSTNAME: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres + DB_PORT: 5432 + DB_USERNAME: ${MEDIA_IMMICH_DB_USER:-immich} + DB_PASSWORD: ${MEDIA_IMMICH_DB_PASSWORD} + DB_DATABASE_NAME: ${MEDIA_IMMICH_DB_NAME:-immich} + REDIS_HOSTNAME: ${CORE_COMPOSE_PROJECT_NAME}_redis + REDIS_PORT: ${CORE_REDIS_PORT:-6379} + IMMICH_MACHINE_LEARNING_URL: http://${MEDIA_COMPOSE_PROJECT_NAME}_immich_ml:3003 + volumes: + - immich_upload:/usr/src/app/upload + - /etc/localtime:/etc/localtime:ro + depends_on: + immich_postgres: + condition: service_healthy + networks: + - compose_network + labels: + - 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-immich-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-immich-redirect-web-secure' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.rule=Host(`${MEDIA_IMMICH_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.entrypoints=web' + # HTTPS router + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.rule=Host(`${MEDIA_IMMICH_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure-compress.compress=true' + - 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure-compress,security-headers@file' + # Service + - 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.loadbalancer.server.port=2283' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Immich Machine Learning - AI inference for faces, search, etc. + immich_ml: + image: ${MEDIA_IMMICH_ML_IMAGE:-ghcr.io/immich-app/immich-machine-learning:release} + container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_ml + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + volumes: + - immich_model_cache:/cache + networks: + - compose_network + labels: + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + volumes: jellyfin_config: name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_config @@ -72,6 +151,12 @@ volumes: name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_cache pinchflat_config: name: ${MEDIA_COMPOSE_PROJECT_NAME}_pinchflat_config + immich_postgres_data: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres_data + immich_upload: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_upload + immich_model_cache: + name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_model_cache networks: compose_network: