Immich Docker Compose: Verified v3.2.4 File and RAM
Immich Docker Compose file verified on v3.2.4: 1.3 GiB idle, 3.3 GiB peak across four containers, plus the update, backup and restore steps we ran.
Evidence: TestedBacked by a Docker run in our lab.How we label evidence
The Immich Docker Compose stack is four containers: the server, machine learning, Valkey (the cache, named redis in the file) and Postgres. We verified the file below on Immich v3.2.4 on Oct 1, 2026 and re-ran it on Oct 2, 2026 with the same image digests. Its health check passed after 13.1 s. It idled at 1.3 GiB once its memory had settled, used up to 2.2 GiB in its first two minutes, and peaked at 3.3 GiB while processing 20 uploaded photos on a 4-vCPU lab host. Plan for 8 GB of RAM. It differs from Immich’s own release file in two ways: named volumes instead of bind mounts, and exact image tags instead of ${IMMICH_VERSION}. Below are the problems we hit, a safe update, and a database backup and restore we ran.
Test box: Immich v3.2.4
Measured in our lab on . The numbers below come from the raw result file.
- Idle RAM
- 1.3 GiB all containers, mean over 30 s after the stack settled (176 s after healthy)
- Startup peak RAM
- 2.2 GiB highest total between healthy and settled
- Peak RAM
- 3.3 GiB under load
- Settled after
- 176 s after the first healthy response
- Idle CPU
- 3.0% of one core, lab host
- Startup
- 13 s to first healthy response
- App version
- v3.2.4
- Tested
- (compose file verified the same day)
- Lab host
- Intel(R) Xeon(R) Processor @ 2.10GHz, 4 cores, 15.7 GiB RAM, x86_64.Ubuntu 24.04.4 LTS, Docker 29.6.2.
- How idle was measured
- After the stack was healthy and set up, we sampled total RAM until it stayed within max(2% of its mean, 10 MiB) over 60 s (at least 120 s, at most 600 s), then measured idle over the next 30 s. Rule in full
- Peak RAM measured
- Under load. Upload 20 generated 4000x3000 JPEGs (about 1 MB each, synthetic gradients and shapes, no people) through the API as the admin user, then wait until every Immich background queue (thumbnails, metadata, CLIP smart search, face detection, ...) is empty. The machine-learning container really runs; the images contain no faces, so face recognition has nothing to find. Peak and mean are taken from the first upload until the queues are idle.
| Container | Idle RAM | Peak RAM (under load) | Image, compressed (download) | Image, unpacked (disk) |
|---|---|---|---|---|
| database | 439 MiB | 449 MiB | 205 MB | 989 MB |
| immich-machine-learning | 163 MiB | 1.8 GiB | 342 MB | 1.4 GB |
| immich-server | 757 MiB | 1.1 GiB | 617 MB | 2.5 GB |
| redis | 5 MiB | 6 MiB | 44 MB | 167 MB |
| Total | 1.3 GiB | 3.3 GiB | 1.2 GB | 5.0 GB |
0 s to 210 s after healthy; the line spans 0 to 2.1 GiB. Dashed line: settled.
Timeline as a table
| Seconds after healthy | Total RAM |
|---|---|
| 30 | 2.1 GiB |
| 60 | 2.1 GiB |
| 90 | 2.1 GiB |
| 120 | 1.7 GiB |
| 150 | 1.3 GiB |
| 180 | 1.3 GiB |
| 210 | 1.3 GiB |
RAM is MiB (220 bytes); image sizes are MB (106 bytes). Per-container peaks happen at different moments, so they do not add up to the total peak.
Download verified compose fileDownload .envRaw result JSONHow we measure
Lab notes (5)
- The .env adds IMMICH_HOST=0.0.0.0: the lab host has no IPv6, and the ML container binds [::] by default and crash-loops without it.
- Compose follows the Immich release file with named volumes instead of bind mounts (see the comments in the compose file).
- Hardware acceleration (GPU/iGPU) is not tested: the lab host has none.
- helper image curlimages/curl:latest was pulled from mirror.gcr.io/curlimages/curl:latest because Docker Hub refused the pull
- Run behind a TLS-intercepting proxy: an extra CA bundle was mounted read-only into every container and the SSL_CERT_FILE-style variables pointed at it (lab-only override, NOT in the published compose). It changes outbound TLS trust, nothing else.
Verified compose file
Four services: immich-server, immich-machine-learning, redis and database. The two Immich images are pinned to v3.2.4, Postgres is pinned to Immich’s tested image tag and Valkey is pinned by digest. Only port 2283 is published. The comments in the file are the lab’s.
# Verified compose for Immich, tested 2026-10-02 by the SelfHostBench lab.
# Lab host: Intel(R) Xeon(R) Processor @ 2.10GHz, 4 vCPU, 15.7 GiB RAM, x86_64, Docker 29.6.2.
# Floating image tags below were replaced by the version resolved at test time;
# the registry digest is in the comment on each image line.
# Measurements: immich.json (schema 2).
# Companion env file: immich.env (save it as .env next to this file).
# Secrets are not published: the lab's values are public. In immich.env:
# DB_PASSWORD=CHANGE_ME_DB_PASSWORD. Replace it with your own before the first start.
# Immich: self-hosted photo and video library with machine-learning search.
#
# Based on the compose file of the current Immich release:
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
# Changes from the release file (everything else is as shipped):
# * the upload and database folders are named volumes instead of the ./library and
# ./postgres bind mounts, so the stack can run on a remote Docker host and `down -v`
# leaves nothing behind. Use bind mounts (UPLOAD_LOCATION / DB_DATA_LOCATION in .env)
# if you want photos in a folder you can see.
# * the extends/hardware-acceleration comments are removed (not tested here).
# Put DB_PASSWORD and friends in the .env file next to this file.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:v3.2.4 # sha256:d317916b28090c33eb36b308464ea391f8b7df1d850fcfea227a39ec879718c2 (version v3.2.4, from label:org.opencontainers.image.version)
volumes:
# Originals, thumbnails, encoded video. This is your photo library: back it up.
- immich-upload:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
image: ghcr.io/immich-app/immich-machine-learning:v3.2.4 # sha256:e16c2f166a8174901959fdf85e2e4c7bd1ebc4b37e0b6655de97c41408a260c4 (version v3.2.4, from label:org.opencontainers.image.version)
volumes:
# Downloaded ML models (CLIP, face detection). Safe to delete, they are fetched again.
- model-cache:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:70739f85ad2ee01a726a965584a0f94895f01b0c60b3cc8b0aeef11eaa6888cf # version 9.1.1 (from exec); no registry tag for it could be verified, so the digest is pinned instead
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 (version 14-vectorchord0.4.3-pgvector0.8.1-pgvectors0.2.0, from label:org.opencontainers.image.version)
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:
# The Immich database (albums, users, search index). Back this up with pg_dump.
- immich-db:/var/lib/postgresql/data
shm_size: 128mb
restart: always
healthcheck:
disable: false
volumes:
immich-upload:
immich-db:
model-cache:The .env goes next to it. It has two changes from Immich’s example.env: a line for IMMICH_HOST (see the first gotcha below) and no UPLOAD_LOCATION or DB_DATA_LOCATION, because this file uses named volumes. We also left out upstream’s commented TZ line. Add one, such as TZ=America/Chicago, if you want Immich’s nightly 2:00 AM database backup to run on your local time rather than UTC.
# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
IMMICH_VERSION=v3
# Connection secret for postgres. You should change it to a random password
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
DB_PASSWORD=CHANGE_ME_DB_PASSWORD
# Immich containers listen on [::] (IPv6) by default, and the machine-learning container
# crash-loops on a host whose kernel has IPv6 disabled ("Address family not supported by
# protocol"). The lab host is one of those, so listen on IPv4 only. Harmless on hosts with
# IPv6; delete the line if you like.
IMMICH_HOST=0.0.0.0
# The values below this line do not need to be changed
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immichThe DB_PASSWORD in the downloadable .env is the placeholder CHANGE_ME_DB_PASSWORD, not the password the lab ran with. Postgres accepts that placeholder as an ordinary password (we tried it on the database image), so replace it in step 4 before the first start.
IMMICH_VERSION in this .env does nothing to the image tags. The tags are written into compose.yaml, and docker compose config --images lists them from there. Immich’s upstream file reads IMMICH_VERSION from .env instead. To change versions with ours, edit the two Immich image lines.
Install steps
We ran these steps in a scratch directory on the lab host on Oct 1, 2026 (Docker 29.6.2, Compose 5.3.1). The numbers on this page come from the lab run in immich.json, not from these manual runs.
-
Install Docker Engine with the Compose plugin. Immich’s docs say to use
docker compose(notdocker-compose) and, if you get errors, to install Docker Engine from Docker’s official repository instead of a distro package (Immich install docs). -
Make a folder.
mkdir immich-app cd immich-app -
Save the two files above as
compose.yamland.envin that folder. The download links are on each block. -
Replace the placeholder database password (
CHANGE_ME_DB_PASSWORDin the downloaded.env) before the first start. We ran this command on a copy of the published file: the line then held 32 hex characters.sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$(openssl rand -hex 16)/" .env -
Start the stack. The first start downloads the images: 1,208 MB compressed in total (table below).
docker compose up -d -
Check that all four containers are healthy, then ping the server.
docker compose ps --format 'table {{.Name}}\t{{.Status}}' curl -s http://localhost:2283/api/server/pingOur output, 50 seconds after the start:
NAME STATUS immich_machine_learning Up 50 seconds (healthy) immich_postgres Up 50 seconds (healthy) immich_redis Up 50 seconds (healthy) immich_server Up 50 seconds (healthy) {"res":"pong"}The ping answered at 20 seconds, while
docker compose psstill showedhealth: starting. Wait for fourhealthyrows. -
Open
http://<host>:2283in a browser. Immich’s post-install page says the first user to register becomes the admin. Not run in our lab: the lab created the admin through the API (setup_admin.pyin the lab repo).
Measured resources
Source: immich.json, run Oct 2, 2026 on the lab host (4 vCPU Intel Xeon @ 2.10GHz, 15.7 GiB RAM, Docker 29.6.2). Idle is the mean of 16 samples over 30 s, taken after the stack’s total memory had settled (176 s after the stack was healthy; rule on the methodology page) and before any upload. Startup peak is the highest total between healthy and settled. Peak is the highest value seen while the load ran. The load was 20 generated 4000x3000 JPEGs (23.1 MB in total) uploaded through the API, then a wait until every Immich job queue was empty. That took 42.0 s, with no failed jobs, and a smart search for the images returned all 20.
| Container | Idle RAM, settled (MiB) | Startup peak (MiB) | Peak RAM under load (MiB) | Image compressed (MB) | Image unpacked (MB) |
|---|---|---|---|---|---|
| immich-server | 757 | 1,631 | 1,078 | 616.7 | 2,480 |
| immich-machine-learning | 163 | 164 | 1,880 | 342.0 | 1,390 |
| database (Postgres) | 439 | 507 | 449 | 205.3 | 989 |
| redis (Valkey) | 5 | 6 | 6 | 44.3 | 167 |
| Stack, highest simultaneous total | 1,364 | 2,246 | 3,409 | 1,208.3 | 5,026 |
The stack peak is the highest sum of all four containers at one instant, so it is lower than the sum of the per-container peaks (3,412 MiB). CPU for the same run:
| Container | Idle CPU, mean (% of one core, lab host) | Load CPU, mean (% of one core, lab host) | Load CPU, max (% of one core, lab host) |
|---|---|---|---|
| immich-server | 0.5 | 19 | 288 |
| immich-machine-learning | 2.3 | 103 | 383 |
| database (Postgres) | 0.00 | 0.8 | 4.6 |
| redis (Valkey) | 0.2 | 0.5 | 1.1 |
| Stack | 3.0 | 123 | 388 |
What the numbers say:
- The machine-learning container is the swing. It held 163 MiB before the upload and reached 1,880 MiB while it processed the images. Immich’s environment variable docs list
MACHINE_LEARNING_MODEL_TTL, the inactivity time before a model is unloaded, with a default of 300 s. On a later 4 GiB-capped stack it fell back to 166 MiB about 5 minutes after its last model load; see Immich RAM requirements. immich-serveruses about 1.5 GiB for roughly its first two minutes, then drops. In the result’s memory timeline (10 s averages) the stack total falls from 2,148 MiB at 100 s after healthy to 1,362 MiB at 130 s and stays there; the server goes from 1,542 MiB to 756 MiB. So the server’s startup peak (1,631 MiB) is higher than its peak under load (1,078 MiB), and its settled idle (757 MiB) is lower than both. We did not investigate why it drops. Our figures before Oct 2 (2,162 MiB idle) were taken inside that first phase; the methodology page explains the change.- By our sizing rule (peak times 1.5, rounded up to 1, 2, 4, 8, 16 or 32 GB) the stack needs 8 GB. Immich’s requirements page says 6 GB minimum and 8 GB recommended, with 4 GB possible if machine learning is disabled. We ran a version of that afterwards on the 16 GB lab host: with machine learning off the stack’s highest reading was 2.1 GiB (in its first two minutes), and it finished the same upload test inside 2 GiB of container memory limits in some runs, not all. That is not a real 4 GB or 2 GB machine, and the full stack lost a machine-learning worker under 4 GiB of limits. The runs, the 4 GiB cap and where our numbers differ from the docs are on the Immich RAM requirements page.
What we did not measure: libraries of thousands of photos or any video transcoding, face recognition on real faces (the generated images have none), hardware acceleration (the lab host has no GPU), and ARM hosts. A real import will use more than our 20-photo run.
Gotchas, upgrade and backup
The machine-learning container crash-loops without IPv6
On our lab host, whose kernel has no IPv6, the first start with Immich’s default settings left immich_machine_learning restarting. The server stayed healthy and answered the ping, so the stack looked fine apart from one container. It kept restarting for as long as we watched. The log (docker compose logs --no-color immich-machine-learning) repeated this:
ERROR connection to ('::', 3003) failed: [Errno 97]
Address family not supported by protocol
We ran Immich’s own release file with IMMICH_VERSION=v3.2.4 for this. Adding IMMICH_HOST=0.0.0.0 to .env and running docker compose up -d again fixed it: all four containers healthy, zero restarts. The environment variable page lists IMMICH_HOST (“listening host”, default 0.0.0.0) for both the server and machine learning, yet our log shows the container trying ::, so set it explicitly. That is why the .env above carries the line. It makes the containers listen on IPv4 only. Not tested in our lab: a host with working IPv6, or an IPv6-only host.
The database password is read once
Immich’s docs say to replace the default postgres password with a random one using only A-Za-z0-9; our downloadable .env carries a placeholder there. Do it before the first start (step 4). We changed DB_PASSWORD in .env on a running stack and ran docker compose up -d. The server could not start: password authentication failed for user "postgres". Putting the old value back and running docker compose up -d again brought the stack back. The database kept the password it was created with, so a later change in .env only changes what the server sends. The compose file does not publish port 5432, so the database is reachable only from the other containers.
Named volumes and bind mounts
Our file keeps photos, the database and the model cache in three named volumes (immich_immich-upload, immich_immich-db, immich_model-cache). The immich_ prefix is the project name, which the file fixes with name: immich, so it does not depend on your folder name. Setting COMPOSE_PROJECT_NAME or -p overrides it, so run docker volume ls if you did. Immich’s release file uses two bind mounts, UPLOAD_LOCATION and DB_DATA_LOCATION, which Immich’s example.env sets to ./library and ./postgres. Both work.
- Named volumes needed no permission changes in our runs. The files live under Docker’s data directory, not in a folder you chose.
- Bind mounts put photos in a path you pick, such as a data disk. The Postgres container makes
./postgresowned by its own user (UID 999 in our run) with mode 700, so you need root to browse or delete it. Immich’s requirements page says the database should be on local SSD storage, never a network share, and not on NTFS or FAT filesystems.
The difference matters for docker compose down -v. Docker removes the named volumes declared in the compose file with -v (docker compose down). With our file, down -v removed all three volumes, photos included. With upstream’s bind-mount file, it removed only immich_model-cache. Use plain docker compose down on a stack you want to keep. If you edit our file to use bind mounts, change the two volume lines yourself: we ran upstream’s bind-mount file, not an edited copy of ours.
Update safely
Immich’s upgrade page says to read the release notes and account for breaking changes before upgrading, and that breaking changes are posted in the project’s GitHub discussions. It also says the server is only compatible with the matching major version of the mobile app and that mobile clients should be upgraded first. It states that downgrading is not supported, so the database backup below is your only way back. Read the notes for the version you move to on the releases page. The v3.2.4 notes describe a patch. Compare the image lines in your compose.yaml with the release’s own docker-compose.yml too: the upgrade page describes a past database migration to VectorChord, so the database image can change between releases.
The steps, in order:
-
Back up the database (next section).
-
Change the tags of the two Immich images in
compose.yaml. Our run went from v3.2.2 to v3.2.4, so replace both version numbers with yours:sed -i 's/:v3\.2\.2/:v3.2.4/' compose.yaml -
Pull and restart.
docker compose pull docker compose up -d -
Confirm the version.
curl -s http://localhost:2283/api/server/versionThe output was
{"major":3,"minor":2,"patch":4,"prerelease":null}, and the admin account created on v3.2.2 could log in. Only the server and machine-learning containers were recreated.
If you use Immich’s own file, the version is IMMICH_VERSION in .env, and the documented update is docker compose pull && docker compose up -d. Not run in our lab as an update on that file. We pin an exact tag rather than v3: Immich’s docs offer v3 or an exact version, and a major-version tag can point at a newer release the next time you pull.
Back up and restore the database
Immich’s backup page says the database dump holds only metadata, so you also need the upload folder, and to back up the database first and the filesystem second. It also says Immich writes automatic database dumps into UPLOAD_LOCATION/backups (daily at 2:00 AM, keeping 14, by default). With our file that folder sits in the same volume as your photos, so it does not protect against losing the volume.
Dump the database and copy the upload volume out of the running server container. postgres and immich are the defaults of DB_USERNAME and DB_DATABASE_NAME in .env; change them here if you changed those:
mkdir -p backups
docker exec immich_postgres pg_dump --clean --if-exists --dbname=immich --username=postgres | gzip > backups/immich-db.sql.gz
docker cp immich_server:/data ./backups/immich-upload
The documented command uses docker exec -t. In our run, -t added a carriage return to every line of the dump (238,712 of them). That dump still restored with exit code 0, but a fresh dump of the restored database differed from the original in 26 trigger-function bodies, which now contained carriage returns. No table data differed. Without -t there were none, so we use the command without it. docker cp is fine for a small library; for a large one use a backup tool.
The restore is Immich’s documented procedure with three changes. The documented first line is docker compose down -v, which would delete the upload volume in our file, so we use docker compose down and remove only the database volume. We drop the documented docker compose pull because the images are pinned. The restore needs a database that no Immich server has touched, so the server container is created but not started until the restore finishes.
Warning: docker volume rm immich_immich-db permanently deletes the current database. Run it only after you have checked that backups/immich-db.sql.gz exists and is not empty. If the volume does not exist, as on a new machine, the error is harmless.
docker compose down
docker volume rm immich_immich-db
docker compose create
docker start immich_postgres
sleep 10
gunzip --stdout backups/immich-db.sql.gz | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | docker exec -i immich_postgres psql --dbname=immich --username=postgres --single-transaction --set ON_ERROR_STOP=on
docker compose up -d
psql exited 0, all four containers came back healthy, and the restored server accepted the admin login created before the dump. The server statistics still counted the photo we had uploaded before the dump. We ran the full cycle on v3.2.4 on Oct 1, 2026, with a stack holding one admin account and one uploaded image.
The database dump holds no photos. To restore them as well, for example after docker compose down -v or on a new machine, copy the upload backup into the server container after docker compose create and before docker compose up -d:
docker cp ./backups/immich-upload/. immich_server:/data
We ran this after docker compose down -v had removed all three volumes, with the database restored from the dump as above. The uploaded photo’s original and thumbnail then loaded from the API.
The same check, a restore into a fresh volume followed by a login that must work, is how we tested a SQLite password vault behind a reverse proxy in Vaultwarden Docker Compose.
Where to run it
- Hardware. Immich idled at 1.3 GiB and peaked at 3.3 GiB under load in our lab. Plan for at least 8 GB of RAM. See Immich RAM requirements, measured.
- VPS. The smallest plan that runs this comfortably has 8 GB of RAM, plus disk for the images (5.0 GB unpacked) and your data.
- Keep it safe. Back up the data volume before you rely on it, and put the app behind a reverse proxy with HTTPS before you expose it to the internet.
How we size: recommended RAM is the highest reading we measured (under load or during startup) times 1.5, rounded up to the next of 1, 2, 4, 8, 16 or 32 GB. The extra half covers the operating system, Docker, file cache and spikes our sampling can miss. Your own data and extra apps need more.
Related
- How the lab measures RAM, CPU and image sizes: methodology.
- Other apps in the same format: the self-hosted apps index.
- Official sources used on this page: the Immich install, requirements, upgrading and backup and restore docs.
Update log
- The downloadable .env no longer carries the lab's database password: DB_PASSWORD is the placeholder CHANGE_ME_DB_PASSWORD, to be replaced in step 4. The lab ran with the old value; measurements are unchanged.
- Linked the new Vaultwarden Docker Compose page from the backup and restore section.
- Re-tested under the new idle-RAM method (we now wait for memory to settle). Idle RAM is 1,364 MiB, was 2,162; peak 3,409 MiB, was 3,324. Compose file re-verified, unchanged.
- Linked the new Immich RAM requirements page; replaced the untested 4 GB and model-unload notes with its results.
- First published. Compose file verified on Immich v3.2.4; update, backup and restore steps run on the same day.