Immich is a free, open-source, self-hosted replacement for Google Photos and iCloud Photos. Its phone apps back up your camera roll automatically, and the web app gives you a timeline, albums, shared libraries, a map, face recognition and a "search for dog on a beach" smart search powered by local machine learning. Your photos never leave your server.
In this guide we will install Immich with Docker Compose using the official docker-compose.yml and example.env from the current release, which run four containers: the server, machine learning, Valkey (Redis) and PostgreSQL with vector search. Then we will look at hardware acceleration and a reverse proxy.
main branch.β Before you start
- A Linux host with Docker and Docker Compose v2 installed (
docker compose versionshould work). Usedocker composewith a space: the old Pythondocker-composev1 can crash on modern Docker. - At least 4 GB of RAM (6 GB or more is better) and 2 CPU cores. Machine learning is the hungry part.
- Enough disk for your library, plus a local SSD for the database. Network shares are not supported for the database folder.
- One free port,
2283, for the web UI and the phone apps. - Optional: a reverse proxy such as Traefik or Nginx Proxy Manager and a domain, if you want to reach it from outside your home network. Strongly recommended if you want phone backups from outside your home.
STEP 1: User and group IDs (and your secrets)
Immich does not use PUID/PGID, so there are no IDs to look up; it manages the permissions of its own folders.
What you do need is a database password. Immich asks for letters and numbers only (A-Za-z0-9), so this is a good way to make one:
openssl rand -hex 24Keep them somewhere safe, such as your password manager. In the examples below they appear as placeholders like CHANGE_ME_STRONG_PASSWORD and change-me.
STEP 2: Create the folders
I keep every app's files under one parent folder so a backup is a single job. Create the folders for Immich:
mkdir -p /path/to/appdata/immich/postgres
mkdir -p /path/to/photos/immich
cd /path/to/appdata/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.envThis downloads the two official files straight from the latest release. The library can live on a big hard drive or a NAS mount; the database should stay on a local SSD.
When you are done, the layout should look like this:
/path/to/appdata/immich/
docker-compose.yml
.env # your settings
postgres/ # the database (local SSD)
/path/to/photos/immich/ # UPLOAD_LOCATION: originals, thumbnails, encoded videoFolder layout
Finally, I give every web app that sits behind my reverse proxy one shared Docker network called proxy. Immich joins it so Traefik or Nginx Proxy Manager can reach it by container name, while its database and helpers stay on the stack's own private network. Create it once (if it already exists, Docker just tells you so):
docker network create proxySTEP 3: The compose file
Open the .env file you downloaded and set these values (everything below the line of # characters can stay as it is):
# The location where your uploaded files are stored
UPLOAD_LOCATION=/path/to/photos/immich
# The location where your database files are stored. Network shares are not supported for the database
DB_DATA_LOCATION=/path/to/appdata/immich/postgres
TZ=Europe/London
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
IMMICH_VERSION=v3
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
DB_PASSWORD=CHANGEMESTRONGPASSWORD
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immich.env
The docker-compose.yml works as downloaded. The only change I make is adding the shared proxy network to immich-server, marked added below. Here is the whole file for reference:
#
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
volumes:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
networks: # added: default for the database, proxy for your reverse proxy
- default
- proxy
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:70739f85ad2ee01a726a965584a0f94895f01b0c60b3cc8b0aeef11eaa6888cf
healthcheck:
test: redis-cli ping | grep -q PONG || exit 1
restart: always
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
# Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs
# DB_STORAGE_TYPE: 'HDD'
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
healthcheck:
disable: false
volumes:
model-cache:
networks: # added
proxy:
external: truedocker-compose.yml (v3.2.4, with the proxy network added)
@sha256:... digests on the Valkey and Postgres images alone. Immich pins exact builds on purpose, and the Postgres image carries the vector-search extensions Immich needs, so a plain postgres image will not work.STEP 4: The settings explained
| Setting | What it does |
|---|---|
UPLOAD_LOCATION |
Where Immich keeps everything you upload: originals plus thumbnails and transcoded videos. This is the folder to back up (together with the database). |
DB_DATA_LOCATION |
The PostgreSQL data folder. Keep it on a local disk, ideally an SSD. |
TZ |
Your time zone. List of zones. |
IMMICH_VERSION |
v3 follows the v3 major version. Pin it to an exact release such as v3.2.4 if you prefer to upgrade on your own schedule. |
DB_PASSWORD |
The database password. Letters and numbers only. Set it before the first start; changing it later needs a change inside Postgres too. |
immich-server |
The API, web app and background jobs. Port 2283 is the web UI and what the phone apps connect to. |
immich-machine-learning |
Runs face detection, face recognition and smart search (CLIP) locally. Models download into the model-cache volume on first use. |
redis (Valkey) |
A fast in-memory job queue. |
database |
PostgreSQL 14 with the VectorChord and pgvecto.rs extensions for vector (smart) search. |
DB_STORAGE_TYPE: 'HDD' |
Uncomment this if the database has to live on a spinning disk; it tunes Postgres for slower storage. |
/etc/localtime |
Gives the server the host's clock settings, read-only. |
Optional: hardware acceleration
Immich can hand video transcoding and machine learning to your GPU. Both use extra compose files from the same release; download them next to your compose file:
cd /path/to/appdata/immich
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml- Transcoding (
hwaccel.transcoding.yml): uncomment theextends:block underimmich-serverand changeservice: cputonvenc(NVIDIA),quicksync(Intel),vaapi(Intel/AMD),rkmpp(Rockchip) orvaapi-wsl. Then pick the same method under Administration β Settings β Video Transcoding β Hardware Acceleration. See the hardware transcoding docs. - Machine learning (
hwaccel.ml.yml): uncomment theextends:block underimmich-machine-learning, setservice:tocuda,rocm,openvino,armnnorrknn, and add the same suffix to the image tag, for example${IMMICH_VERSION:-release}-cuda. See the ML acceleration docs.
NVIDIA needs the NVIDIA Container Toolkit installed on the host first. If you are not sure, start on CPU: everything works, it is just slower.
STEP 5: Start Immich
From the folder containing your compose file:
cd /path/to/appdata/immich
docker compose up -d
docker compose logs -fWait until the logs settle down (press Ctrl+C to stop following them; the container keeps running), then check it is up:
docker ps --filter name=immichYou should see immich_server, immich_machine_learning, immich_redis and immich_postgres. The first start takes a minute or two while the database is created; the server shows (healthy) when it is ready.
STEP 6: First-run setup
- Open
http://192.168.1.20:2283and click Getting Started. - Create the admin account. The first user to register is the administrator.
- Go through the short onboarding (theme, storage template, privacy settings). The storage template decides how files are named on disk, for example
{{y}}/{{MM}}/{{filename}}; it is easiest to choose before you upload a lot. - Under Administration β Users, create accounts for the rest of the family.
- Install the Immich app on your phone, enter your server URL (
http://192.168.1.20:2283at home, your proxy hostname outside) and turn on Backup.
Add it as an External Library (Administration β External Libraries) instead of uploading it. Mount the folder read-only into
immich-server, for example - /path/to/photos/archive:/mnt/archive:ro, and Immich indexes it in place without copying anything.STEP 7: Put Immich behind a reverse proxy
Immich serves the web UI, API and WebSockets on port 2283. Photo and video uploads can be very large, so the proxy must not cap the request size. Use a dedicated hostname such as photos.example.com; sub-paths are not supported.
Option A: Traefik (Docker labels)
If Traefik runs on the same Docker host and is attached to the proxy network, add these labels to the immich service. They assume your HTTPS entry point is called websecure and your certificate resolver is called cloudflare; change them to match your Traefik config.
labels:
- traefik.enable=true
- traefik.http.routers.immich.rule=Host(`photos.example.com`)
- traefik.http.routers.immich.entrypoints=websecure
- traefik.http.routers.immich.tls.certresolver=cloudflare
- traefik.http.services.immich.loadbalancer.server.port=2283
- traefik.docker.network=proxydocker-compose.yml (extra lines)
Option B: Traefik (file provider)
If Traefik runs on a different machine, add a router and service to your dynamic config file instead:
http:
routers:
immich:
rule: Host(`photos.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: immich
services:
immich:
loadBalancer:
servers:
- url: http://192.168.1.20:2283dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for photos.example.com with scheme http, forward hostname immich (if NPM is on the proxy network) or 192.168.1.20, and forward port 2283. Request a certificate on the SSL tab and tick Force SSL. Turn Websockets Support on, and under the Advanced tab add client_max_body_size 50000M; plus longer timeouts (proxy_read_timeout 600s; and proxy_send_timeout 600s;) so big video uploads do not fail.
Put the labels on immich-server. The extra traefik.docker.network=proxy label matters because the server is on two networks. Traefik does not limit body size by default. See the official reverse proxy docs for more examples.
Updating Immich
With IMMICH_VERSION=v3, a pull fetches the newest v3 release:
cd /path/to/appdata/immich
docker compose pull
docker compose up -d
docker image prune -f.env; when it does, download the new files from the release and copy your settings across. Make sure Immich's automatic database backups are on (Administration β Settings β Backup) and back up UPLOAD_LOCATION too.Troubleshooting
immich_postgres keeps restarting. Check docker logs immich_postgres. The database folder must be on a local disk (not SMB/NFS), and DB_PASSWORD must only contain letters and numbers.
Smart search or faces do nothing. Machine learning is still downloading models, or the container ran out of memory. Check docker logs immich_machine_learning; with little RAM, use a smaller CLIP model under Administration β Settings β Machine Learning.
Phone uploads fail through the proxy but work at home. Your proxy has a body-size limit or short timeouts. Raise them as shown in STEP 7.
Hardware transcoding errors. The device is missing in the container (for example /dev/dri for Quick Sync/VAAPI) or the NVIDIA toolkit is not installed. Go back to service: cpu until it is fixed.
That's it! Your own Google Photos, minus Google. Let the phone app chew through that camera roll overnight. πΈ