diff --git a/.env.example b/.env.example
index 9835303..3374992 100644
--- a/.env.example
+++ b/.env.example
@@ -1,18 +1,20 @@
COMPOSE_PROFILES=
COMPOSE_PATH_SEPARATOR=:
-COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml:homeassistant/docker-compose.yml
+COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml:homeassistant/docker-compose.yml:immich/docker-compose.yml
USER_ID=1000
GROUP_ID=1000
TIMEZONE="America/New_York"
CONFIG_ROOT="."
DATA_ROOT="/mnt/data"
DOWNLOAD_ROOT="/mnt/data/torrents"
+IMMICH_UPLOAD_LOCATION="/mnt/data/photos"
PIA_LOCATION=ca
PIA_USER=
PIA_PASS=
PIA_LOCAL_NETWORK="192.168.0.0/16"
HOSTNAME=localhost
HOMEASSISTANT_HOSTNAME=
+IMMICH_HOSTNAME=
ADGUARD_HOSTNAME=
ADGUARD_USERNAME=
ADGUARD_PASSWORD=
@@ -33,6 +35,7 @@ BAZARR_API_KEY=
JELLYFIN_API_KEY=
JELLYSEERR_API_KEY=
SABNZBD_API_KEY=
+IMMICH_API_KEY=
HOMEASSISTANT_ACCESS_TOKEN=
HOMEPAGE_VAR_TITLE="Docker-Compose NAS"
HOMEPAGE_VAR_SEARCH_PROVIDER=google
@@ -41,3 +44,4 @@ HOMEPAGE_VAR_WEATHER_CITY=
HOMEPAGE_VAR_WEATHER_LAT=
HOMEPAGE_VAR_WEATHER_LONG=
HOMEPAGE_VAR_WEATHER_UNIT=metric
+IMMICH_DB_PASSWORD=postgres
\ No newline at end of file
diff --git a/README.md b/README.md
index 810d259..cd5c700 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology
* [Tandoor](#tandoor)
* [Joplin](#joplin)
* [Home Assistant](#home-assistant)
+ * [Immich](#immich)
* [Customization](#customization)
* [Optional: Using the VPN for *arr apps](#optional-using-the-vpn-for-arr-apps)
* [Synology Quirks](#synology-quirks)
@@ -77,6 +78,7 @@ I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology
| [Tandoor](https://tandoor.dev) | Optional - Smart recipe management
Enable with `COMPOSE_PROFILES=tandoor` | [vabene1111/recipes](https://hub.docker.com/r/vabene1111/recipes) | /recipes |
| [Joplin](https://joplinapp.org/) | Optional - Note taking application
Enable with `COMPOSE_PROFILES=joplin` | [joplin/server](https://hub.docker.com/r/joplin/server) | /joplin |
| [Home Assistant](https://www.home-assistant.io/) | Optional - Open source home automation that puts local control and privacy first
Enable with `COMPOSE_PROFILES=homeassistant` | [home-assistant/home-assistant:stable](https://ghcr.io/home-assistant/home-assistant) | |
+| [Immich](https://immich.app//) | Optional - Self-hosted photo and video management solution
Enable with `COMPOSE_PROFILES=immich` | [immich-app/immich-server:release](https://ghcr.io/immich-app/immich-server) | |
Optional containers are not enabled by default, they need to be enabled,
see [Optional Services](#optional-services) for more information.
@@ -389,6 +391,10 @@ See [here](./joplin/README.md).
See [here](./homeassistant/README.md).
+### Immich
+
+See [here](./immich/README.md).
+
## Customization
You can override the configuration of a service or add new services by creating a new `docker-compose.override.yml` file,
diff --git a/immich/.gitignore b/immich/.gitignore
new file mode 100644
index 0000000..9a5f6bf
--- /dev/null
+++ b/immich/.gitignore
@@ -0,0 +1,4 @@
+*
+!README.md
+!docker-compose.yml
+!healthcheck
\ No newline at end of file
diff --git a/immich/README.md b/immich/README.md
new file mode 100644
index 0000000..1ccd77e
--- /dev/null
+++ b/immich/README.md
@@ -0,0 +1,20 @@
+# Immich
+
+Self-hosted photo and video management solution
+
+## Installation
+
+Enable Immich by setting `COMPOSE_PROFILES=immich`.
+
+Set the `IMMICH_HOSTNAME`, since it does not support
+[running in a subfolder](https://github.com/immich-app/immich/discussions/1679#discussioncomment-7276351).
+Add the necessary DNS records in your domain.
+
+## Environment Variables
+
+| Variable | Description | Default |
+|--------------------------|------------------------------------------------------|--------------------|
+| `IMMICH_HOSTNAME` | URL Immich will be accessible from | |
+| `IMMICH_UPLOAD_LOCATION` | Path where the assets will be stored | `/mnt/data/photos` |
+| `IMMICH_API_KEY` | Immich API key to show information in the homepage | `1000` |
+| `IMMICH_DB_PASSWORD` | Postgres database password, change for more security | `postgres` |
diff --git a/immich/docker-compose.yml b/immich/docker-compose.yml
new file mode 100644
index 0000000..9a3f6c5
--- /dev/null
+++ b/immich/docker-compose.yml
@@ -0,0 +1,114 @@
+services:
+ immich-server:
+ container_name: immich_server
+ image: ghcr.io/immich-app/immich-server:release
+ environment:
+ DB_HOSTNAME: immich_postgres
+ DB_PASSWORD: ${IMMICH_DB_PASSWORD}
+ DB_USERNAME: postgres
+ DB_DATABASE_NAME: immich
+ command: ['start.sh', 'immich']
+ volumes:
+ - ${IMMICH_UPLOAD_LOCATION}:/usr/src/app/upload
+ - /etc/localtime:/etc/localtime:ro
+ - ${CONFIG_ROOT:-.}/immich/healthcheck:/healthcheck
+ depends_on:
+ - immich-redis
+ - immich-database
+ restart: always
+ healthcheck:
+ test: [ "CMD", "node", "/healthcheck/healthcheck.js" ]
+ interval: 30s
+ retries: 10
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.immich.rule=(Host(`${IMMICH_HOSTNAME}`))
+ - traefik.http.routers.immich.tls=true
+ - traefik.http.routers.immich.tls.certresolver=myresolver
+ - traefik.http.services.immich.loadbalancer.server.port=3001
+ - homepage.group=Apps
+ - homepage.name=immich
+ - homepage.icon=immich.png
+ - homepage.href=https://${IMMICH_HOSTNAME}
+ - homepage.description=Self-hosted photo and video management solution
+ - homepage.weight=4
+ - homepage.widget.type=immich
+ - homepage.widget.url=http://immich:3001
+ - homepage.widget.key=${IMMICH_API_KEY}
+ profiles:
+ - immich
+
+ immich-microservices:
+ container_name: immich_microservices
+ image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
+ environment:
+ DB_HOSTNAME: immich_postgres
+ DB_PASSWORD: ${IMMICH_DB_PASSWORD}
+ DB_USERNAME: postgres
+ DB_DATABASE_NAME: immich
+ devices:
+ - /dev/dri/renderD128:/dev/dri/renderD128
+ - /dev/dri/card0:/dev/dri/card0
+ command: ['start.sh', 'microservices']
+ volumes:
+ - ${IMMICH_UPLOAD_LOCATION}:/usr/src/app/upload
+ - /etc/localtime:/etc/localtime:ro
+ depends_on:
+ - immich-redis
+ - immich-database
+ restart: always
+ healthcheck:
+ test: [ "CMD", "bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3002" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ profiles:
+ - immich
+
+ immich-machine-learning:
+ container_name: immich_machine_learning
+ image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
+ volumes:
+ - immich-model-cache:/cache
+ restart: always
+ healthcheck:
+ test: [ "CMD", "bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3003" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ profiles:
+ - immich
+
+ immich-redis:
+ container_name: immich_redis
+ image: registry.hub.docker.com/library/redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
+ restart: always
+ healthcheck:
+ test: [ "CMD", "redis-cli", "ping" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ profiles:
+ - immich
+
+ immich-database:
+ container_name: immich_postgres
+ image: registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
+ environment:
+ DB_HOSTNAME: immich_postgres
+ POSTGRES_PASSWORD: ${IMMICH_DB_PASSWORD}
+ POSTGRES_USER: postgres
+ POSTGRES_DB: immich
+ volumes:
+ - ${CONFIG_ROOT:-.}/immich/postgresql:/var/lib/postgresql/data
+ restart: always
+ healthcheck:
+ test: [ "CMD-SHELL", "pg_isready" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ profiles:
+ - immich
+
+volumes:
+ immich-model-cache:
diff --git a/immich/healthcheck/healthcheck.js b/immich/healthcheck/healthcheck.js
new file mode 100644
index 0000000..0e627ea
--- /dev/null
+++ b/immich/healthcheck/healthcheck.js
@@ -0,0 +1,28 @@
+// Inspired by: https://anthonymineo.com/docker-healthcheck-for-your-node-js-app/
+const http = require('http');
+const options = {
+ host: '127.0.0.1',
+ port: 3001,
+ timeout: 2000,
+ path: '/api/server-info/ping',
+ headers: {
+ 'Host': process.env.HOSTNAME,
+ }
+};
+
+const healthCheck = http.request(options, (res) => {
+ console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
+ if (res.statusCode === 200) {
+ process.exit(0);
+ }
+ else {
+ process.exit(1);
+ }
+});
+
+healthCheck.on('error', function (err) {
+ console.error('ERROR:' + err);
+ process.exit(1);
+});
+
+healthCheck.end();