Productivity & Home
Immich logo

How to install Immich with Docker

Immich cover: How to install Immich with Docker
Difficulty: IntermediateTime: 30 minutesTested with: Immich v3.2 (official compose file)

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.

πŸ’‘
Note: This guide works with the current Immich v3.2 release (v3.2.4 at the time of writing). Immich moves quickly: always start from the compose file attached to the current release, not the one on the main branch.

βœ… Before you start

  • A Linux host with Docker and Docker Compose v2 installed (docker compose version should work). Use docker compose with a space: the old Python docker-compose v1 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 24

Keep 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.env

This 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 video

Folder 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 proxy

STEP 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: true

docker-compose.yml (v3.2.4, with the proxy network added)

⚠️
Warning: Leave the @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 the extends: block under immich-server and change service: cpu to nvenc (NVIDIA), quicksync (Intel), vaapi (Intel/AMD), rkmpp (Rockchip) or vaapi-wsl. Then pick the same method under Administration β†’ Settings β†’ Video Transcoding β†’ Hardware Acceleration. See the hardware transcoding docs.
  • Machine learning (hwaccel.ml.yml): uncomment the extends: block under immich-machine-learning, set service: to cuda, rocm, openvino, armnn or rknn, 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 -f

Wait 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=immich

You 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

  1. Open http://192.168.1.20:2283 and click Getting Started.
  2. Create the admin account. The first user to register is the administrator.
  3. 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.
  4. Under Administration β†’ Users, create accounts for the rest of the family.
  5. Install the Immich app on your phone, enter your server URL (http://192.168.1.20:2283 at home, your proxy hostname outside) and turn on Backup.
πŸ‘
Tip: Already have a big photo archive?
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=proxy

docker-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:2283

dynamic.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
⚠️
Warning: Always read the release notes first. Immich occasionally makes breaking changes to the compose file or .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. πŸ“Έ