Immich RAM Requirements: Measured Idle and Peak RAM
Immich RAM requirements measured on v3.2.4: 1.3 GiB idle, 3.3 GiB peak with machine learning, 1.1 GiB idle without. Tested under 4 and 2 GiB caps.
Evidence: TestedBacked by a Docker run in our lab.How we label evidence
Tested: Immich v3.2.4 on · idle 1.3 GiB · peak 3.3 GiB
Tested: Immich (4 GiB cap) v3.2.4 on · idle 1.2 GiB · peak 3.1 GiB
Tested: Immich (no machine learning, 2 GiB cap) v3.2.4 on · idle 1.1 GiB · peak 1.5 GiB
Plan on 8 GB of RAM for Immich with machine learning on, and 4 GB with it off. That is our sizing rule (peak times 1.5, rounded up) applied to measurements on v3.2.4: the full four-container stack idled at 1.3 GiB once its memory had settled, used up to 2.2 GiB during its first two minutes, and peaked at 3.3 GiB processing 20 uploaded photos. With machine learning off it idled at 1.1 GiB and peaked at 1.5 GiB under load (2.1 GiB at start-up). Immich’s docs say 6 GB minimum, 8 GB recommended. We also set container memory limits on a 16 GB host. That is not a real 2 GB or 4 GB machine. Under 4 GiB of limits the full stack finished the test, but an out-of-memory kill hit the machine-learning container in all five runs. Without machine learning, 2 GiB of limits worked in 3 of 8 runs on Oct 2: in the other five the database was killed on first start. Setup steps are on the Immich Docker Compose page.
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
The stack is the same as in the verified compose file and setup guide, so that file is not repeated here. The RAM variants we ran are small edits of it. This is the file for the stack without machine learning: the immich-machine-learning service and the model-cache volume are removed. The 4 GiB and 2 GiB runs add one mem_limit line to each service (the split is in the file header); download them from the table under Measured resources or from the Test boxes.
# Verified compose for Immich (no machine learning), 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-no-ml.json (schema 2).
# Companion env file: immich-no-ml.env (save it as .env next to this file).
# Secrets are not published: the lab's values are public. In immich-no-ml.env:
# DB_PASSWORD=CHANGE_ME_DB_PASSWORD. Replace it with your own before the first start.
# Immich: self-hosted photo and video library (this variant: machine learning disabled).
#
# 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.
#
# VARIANT (SelfHostBench lab): same stack, WITHOUT machine learning.
# Done the way Immich's FAQ describes (https://docs.immich.app/FAQ#how-can-i-disable-machine-learning):
# 1. Machine learning is switched off under Administration > Settings > Machine Learning
# (the lab does this through the API in disable_ml.py), and
# 2. the `immich-machine-learning` section is removed from the compose file, so the container never starts.
# The model-cache volume is removed with it. No memory limits.
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
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:Install steps
Start from the setup in the Immich Docker Compose guide, but use the file above with its .env, and start it. The downloadable .env has the placeholder CHANGE_ME_DB_PASSWORD for DB_PASSWORD; replace it first, as in step 4 of that guide. The lab ran with its own database password; the numbers do not depend on it.
docker compose up -d
Machine learning also has to be switched off in Immich’s settings. Immich’s FAQ says to do that under Administration > Settings > Machine Learning Settings, and that disabling every job does not stop the service itself, so you also remove the immich-machine-learning section from the compose file. Not run in our lab: the settings screen. The lab made the same change through the admin settings API (disable_ml.py, which prints machineLearning.enabled after: False).
To check memory and kills on a running stack we used these commands. The first prints each container’s memory use, the second its restart count and whether the kernel killed it for memory.
docker stats --no-stream --format '{{.Name}}={{.MemUsage}}' immich_machine_learning immich_server
docker inspect -f 'RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} MemLimit={{.HostConfig.Memory}} Status={{.State.Status}}' immich_machine_learning
docker logs immich_machine_learning
Measured resources
All numbers come from lab results on one host (4 vCPU Intel Xeon @ 2.10GHz, 15.7 GiB RAM, Docker 29.6.2), Oct 2, 2026, Immich v3.2.4. Idle is the mean of 16 samples over 30 s, taken after the stack’s total memory had settled (170 to 264 s after the stack was healthy; the rule is on the methodology page). Startup peak is the highest total between healthy and settled. Peak is the highest simultaneous total while the load ran. The load was the same every time: 20 generated 4000x3000 JPEGs (23.1 MB) uploaded through the API, then a wait until every job queue was empty. The images have no faces, and there is no video. Memory is the working set docker stats reports. The upload took about 1 s in every run. The extra runs mentioned below (repeats of the capped stacks) are in the lab notes, not in the result files.
| Run, version, test date, files | Idle RAM, settled (MiB) | Startup peak (MiB) | Peak RAM under load (MiB) | Time until all queues idle (s) | Settled after healthy (s) | Memory limits |
|---|---|---|---|---|---|---|
| Full stack Immich v3.2.4, tested 2026‑10‑02 Result JSON, compose.yaml, .env |
1,364 | 2,246 | 3,409 | 42.0 | 176 | none |
| Full stack, ML job concurrency 1 Immich v3.2.4, tested 2026‑10‑02 Result JSON, compose.yaml, .env |
1,352 | 2,215 | 3,363 | 36.2 | 170 | none |
| No machine learning Immich v3.2.4, tested 2026‑10‑02 Result JSON, compose.yaml, .env |
1,120 | 2,132 | 1,520 | 11.7 | 171 | none |
| Full stack, 4 GiB of caps Immich v3.2.4, tested 2026‑10‑02 Result JSON, compose.yaml, .env |
1,247 | 1,776 | 3,170 | 51.2 | 264 | 4,096 MiB in total |
| No machine learning, 2 GiB of caps Immich v3.2.4, tested 2026‑10‑02 Result JSON, compose.yaml, .env |
1,139 | 1,636 | 1,522 | 11.7 | 175 | 2,048 MiB in total |
The “Settled after healthy” column is when the stack’s total memory counted as settled, the point where the idle measurement started. Each result file is the raw output of one run.
Per container, full stack
| Container | Idle RAM, settled (MiB) | Startup peak (MiB) | Peak RAM under load (MiB) |
|---|---|---|---|
| immich-server | 757 | 1,631 | 1,078 |
| immich-machine-learning | 163 | 164 | 1,880 |
| database (Postgres) | 439 | 507 | 449 |
| redis (Valkey) | 5 | 6 | 6 |
| Stack | 1,364 | 2,246 | 3,409 |
The machine-learning container is the swing: 163 MiB idle and 1,880 MiB at its peak. The peaks of the four containers do not happen at the same instant, which is why the stack’s peak is lower than the sum of the four (3,412 MiB). Source: immich.json.
Without machine learning
Switching machine learning off cut the load peak from 3,409 MiB to 1,520 MiB, and the job queues emptied in 11.7 s instead of 42.0 s, with the machine-learning jobs disabled. The server (726 MiB idle, 1,119 MiB at its load peak, 1,534 MiB at start-up), Postgres (388 MiB idle, 396 MiB at its load peak, 610 MiB at start-up) and Valkey (5 MiB) stayed. In this run the stack’s start-up peak (2,132 MiB, in the first two minutes) was higher than its load peak (1,520 MiB). By our rule, which uses the load peak, that needs 4 GB of RAM. Sizing from the start-up peak gives the same answer: 2,132 MiB times 1.5 is 3.1 GiB, which rounds up to 4 GB (immich-no-ml.json).
How long “idle” lasts
The server container uses about 1.5 GiB for roughly its first two minutes after the stack is healthy, then drops to about 0.7 GiB. We now wait for that before we measure idle (rule on the methodology page). The memory timelines in the result files show it: in immich-no-ml.json the server holds about 1.5 GiB (1,504 to 1,519 MiB in each 10 s average from 20 s to 100 s after healthy), is at 752 MiB at 120 s, and stays at 725 to 727 MiB to the end of the idle window, about 3.5 minutes after healthy. We also watched the no-ML stack by hand for 10 minutes on Oct 1, with no photos uploaded and no memory limits (lab notes, immich-memory-caps-2026-10-01.md, section 6): the server fell from 1.4 to 1.5 GiB to about 730 MiB between roughly 1:40 and 2:10 after healthy, and stayed between 655 and 730 MiB to the end. Postgres sat at about 419 MiB, Valkey at 5 MiB. We did not investigate why the server’s memory falls. Until Oct 2 this page quoted idle figures taken 70 to 100 s after the stack was healthy, inside the first phase (2,162 MiB for the full stack, now 1,364). The methodology page describes the change, and the old and new values are side by side in the lab notes (methodology-change-2026-10-02.md). We watched one hand-run and a handful of timelines, so we do not know how long the phase lasts on other hosts, or what the full stack does beyond 3.5 minutes. We still size from the peaks.
Can Immich run on 4 GB?
Without machine learning, probably yes. With it, not comfortably. Everything below comes from container memory limits on a 16 GB host, not from a real 4 GB machine. Immich’s requirements page says: “For systems with only 4GB of RAM, Immich can be run with machine learning features disabled.” Our no-ML stack’s highest reading was 2.1 GiB (2,132 MiB, at start-up); it idled at 1.1 GiB and peaked at 1.5 GiB under load. A 4 GB host is about 3.7 GiB, so that would leave roughly 1.6 GiB for the operating system at the highest reading, if a real 4 GB host behaves like our limits did. We did not test one.
With machine learning on, we ran the full stack with a hard memory limit on every container, adding up to 4,096 MiB: server 1,536 MiB, machine learning 1,664 MiB, database 768 MiB, Valkey 128 MiB. The same upload test finished: all 20 uploads were processed and no job failed (immich-4g.json, queues idle after 51.2 s). But the machine-learning container hit its limit in every run: five of five, two on Oct 1 and three on Oct 2. In each, OOMKilled was true and the restart count stayed 0, so the container never stopped. On Oct 1, Docker’s event log showed an out-of-memory event during the load, and the ML log of the second run read Worker (pid:15) was sent SIGKILL! Perhaps out of memory? two seconds after it began loading the buffalo_l face model, then booted a new worker. So the kernel killed a worker process and the service replaced it. Raw output for Oct 1 is in the lab notes (section 3), and the Oct 2 runs are in methodology-change-2026-10-02.md (section 4.2). In the committed Oct 2 run (immich-4g.json) the ML container reached 1,664 MiB, exactly its limit, and a smart search for the 20 images returned only 14 of them, although no job was reported failed. The other four runs returned all 20. We saw this once in five runs and did not investigate the cause, so treat it as a possible cost of the cap, not a measured rate.
Test box: Immich (4 GiB cap) v3.2.4
Measured in our lab on . The numbers below come from the raw result file.
- Idle RAM
- 1.2 GiB all containers, mean over 30 s after the stack settled (264 s after healthy)
- Startup peak RAM
- 1.7 GiB highest total between healthy and settled
- Peak RAM
- 3.1 GiB under load
- Settled after
- 264 s after the first healthy response
- Idle CPU
- 4.0% of one core, lab host
- Startup
- 8.9 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. Every container has a hard memory limit; the limits add up to 4 GiB. 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 | 389 MiB | 400 MiB | 205 MB | 989 MB |
| immich-machine-learning | 162 MiB | 1.6 GiB | 342 MB | 1.4 GB |
| immich-server | 692 MiB | 1.1 GiB | 617 MB | 2.5 GB |
| redis | 5 MiB | 6 MiB | 44 MB | 167 MB |
| Total | 1.2 GiB | 3.1 GiB | 1.2 GB | 5.0 GB |
0 s to 300 s after healthy; the line spans 0 to 1.7 GiB. Dashed line: settled.
Timeline as a table
| Seconds after healthy | Total RAM |
|---|---|
| 30 | 1.6 GiB |
| 60 | 1.6 GiB |
| 90 | 1.7 GiB |
| 120 | 1.5 GiB |
| 150 | 1.3 GiB |
| 180 | 1.3 GiB |
| 210 | 1.2 GiB |
| 240 | 1.2 GiB |
| 270 | 1.2 GiB |
| 300 | 1.2 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 (7)
- Warning: immich-machine-learning was OOM-killed (State.OOMKilled is true)
- Memory caps are compose `mem_limit` values per container (hard limits: a container over its limit is OOM-killed). Docs: https://docs.immich.app/FAQ#can-i-limit-cpu-and-ram-usage (read 2026-10-01). The split and its sum are in the header of compose.yaml. RestartCount and OOMKilled per container are in services.<name>.restart_count / oom_killed.
- The .env adds IMMICH_HOST=0.0.0.0: the lab host has no IPv6, and Immich containers listen on [::] by default (the ML container crash-loops without it).
- Same stack, .env and upload load as apps/immich (Immich v3 release image tags, 20 generated 4000x3000 JPEGs); the setup and upload scripts are shared with that app.
- 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.
What this does not show: a 4 GB machine. The caps add up to 4 GiB (4,096 MiB, about 4.3 GB) for the containers alone, more than a 4 GB host has in total. A real 4 GB host (about 3.7 GiB) also runs the operating system, Docker and the file cache. Our uncapped full-stack peak of 3.3 GiB would leave about 0.4 GiB for all of that, and the ML container already lost a worker with 1,664 MiB. We would not run the full stack on 4 GB. We would turn machine learning off, as the docs say. Photos with faces and a library larger than 20 images are not in this test.
Can Immich run on 2 GB?
Without machine learning, in containers capped at 2,048 MiB in total, only sometimes. We ran this exact setup 8 times on Oct 2 (5 times with the new harness, 3 times with the previous one, to rule out the new sampling as the cause). It finished the same upload test 3 times (immich-no-ml-2g.json is one of them; in the two runs whose results we kept, no container was killed or restarted). The other 5 failed during setup, before any upload: Postgres hit its 640 MiB limit during the first start and was killed, and the server, which lost its database connection, exited and restarted once. The split was server 1,344 MiB, database 640 MiB and Valkey 64 MiB. In the committed run the server’s peak under load was 1,092 MiB (1,207 MiB at start-up), the database’s 426 MiB, and the stack peaked at 1,522 MiB (1,636 MiB at start-up). Raw results are in methodology-change-2026-10-02.md, section 4.1.
Test box: Immich (no machine learning, 2 GiB cap) v3.2.4
Measured in our lab on . The numbers below come from the raw result file.
- Idle RAM
- 1.1 GiB all containers, mean over 30 s after the stack settled (175 s after healthy)
- Startup peak RAM
- 1.6 GiB highest total between healthy and settled
- Peak RAM
- 1.5 GiB under load
- Settled after
- 175 s after the first healthy response
- Idle CPU
- 1.7% of one core, lab host
- Startup
- 9.5 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. Machine learning is disabled and its container is not running. Every container has a hard memory limit; the limits add up to 2 GiB. 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 | 418 MiB | 426 MiB | 205 MB | 989 MB |
| immich-server | 716 MiB | 1.1 GiB | 617 MB | 2.5 GB |
| redis | 4 MiB | 6 MiB | 44 MB | 167 MB |
| Total | 1.1 GiB | 1.5 GiB | 866 MB | 3.6 GB |
0 s to 210 s after healthy; the line spans 0 to 1.5 GiB. Dashed line: settled.
Timeline as a table
| Seconds after healthy | Total RAM |
|---|---|
| 30 | 1.5 GiB |
| 60 | 1.5 GiB |
| 90 | 1.5 GiB |
| 120 | 1.3 GiB |
| 150 | 1.1 GiB |
| 180 | 1.1 GiB |
| 210 | 1.1 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 (6)
- Machine learning is disabled the way Immich's FAQ describes (https://docs.immich.app/FAQ#how-can-i-disable-machine-learning, read 2026-10-01): switched off under Administration > Settings > Machine Learning Settings (done through the API by disable_ml.py), and the immich-machine-learning section removed from compose.yaml so the container does not start.
- Memory caps are compose `mem_limit` values per container (hard limits: a container over its limit is OOM-killed). Docs: https://docs.immich.app/FAQ#can-i-limit-cpu-and-ram-usage (read 2026-10-01). The split and its sum are in the header of compose.yaml. RestartCount and OOMKilled per container are in services.<name>.restart_count / oom_killed.
- The .env adds IMMICH_HOST=0.0.0.0: the lab host has no IPv6, and Immich containers listen on [::] by default (the ML container crash-loops without it).
- Same stack, .env and upload load as apps/immich (Immich v3 release image tags, 20 generated 4000x3000 JPEGs); the setup and upload scripts are shared with that app.
- Hardware acceleration (GPU/iGPU) is not tested: the lab host has none.
- 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.
The split mattered. We tried three on Oct 1, and the first two failed before the load started (lab notes, section 4). The third is the split we then repeated on Oct 2:
| Server limit (MiB) | Database limit (MiB) | Valkey limit (MiB) | Outcome |
|---|---|---|---|
| 1,472 | 512 | 64 | Failed. Postgres was killed for memory during the first-start geodata import, and the server exited. |
| 1,216 | 768 | 64 | Failed. The server was killed for memory while starting and restarted once. |
| 1,344 | 640 | 64 | Worked on Oct 1 (once) and in 3 of 8 runs on Oct 2: the upload test finished (in the two Oct 2 runs whose results we kept: no kills, no restarts, 20 of 20 uploads processed). In the other 5 runs the database was killed on first start, as in the first row. |
So a 2 GiB budget leaves almost no choice in the split, that is for containers alone, and even the split that worked is not reliable on first start. Our 2,048 MiB of limits is also more than a real 2 GB machine has (about 1.9 GiB), and the operating system takes a share of that. By our sizing rule the no-ML stack needs 4 GB. We would not run Immich on 2 GB: the database limit that fits in 2 GiB failed on first start in 5 of 8 runs. We did not run the full stack with machine learning at 2 GiB: its start-up peak on our uncapped run (2,246 MiB) is already above 2 GiB, and its peak under load was 3,409 MiB. Not tested in our lab: 2 GB with machine learning.
Why Immich RAM usage is high, and how to reduce it
High usage has four sources in our numbers, and they are not all permanent.
- The machine-learning container. It is small until a job needs a model (163 MiB idle) and reached 1,880 MiB at its peak. By default Immich unloads idle models after 300 seconds: the environment variable docs list
MACHINE_LEARNING_MODEL_TTL(“Inactivity time (s) before a model is unloaded”) with a default of300. On the kept 4 GiB stack the container fell from 775 MiB to 166 MiB about 5 minutes after its last model load. In the log the unload is a worker restart (Worker (pid:107) was sent SIGINT!), which Immich’s FAQ says is a feature to reduce RAM when the service is not used. We did not change the TTL value. Lab notes, section 5. - The server’s start-up phase. About 1.5 GiB for the first couple of minutes, then 0.75 GiB (
immich.json: 1,631 MiB at start-up, 757 MiB settled; section above). It also needs room to start: capped at 1,216 MiB it was killed on first start on Oct 1, and at 1,344 MiB it started. - Postgres. About 0.4 GiB in every run, and the docs say its files are “typically between 1-3 GB in size” (requirements page).
- The first import. Immich’s FAQ says the initial backup is the most intensive phase because many jobs run at once. Our 20-photo test is a small one.
What reduces it, and what we saw:
- Disable machine learning. The biggest lever we measured: 3,409 MiB to 1,520 MiB at peak, and 1,364 MiB to 1,120 MiB idle. The cost, per the FAQ: “Disabling machine learning will result in a poor experience for searching and the ‘Explore’ page”. You can also turn off single models (for example only face recognition) in the same settings; not tested in our lab.
- Set a hard limit per container. The FAQ shows how and warns that “memory constraints work by terminating the container, so this can introduce instability if set too low”. Our caps did that: Postgres and the server were killed in the failed 2 GiB attempts, and the machine-learning container had an out-of-memory kill in all five 4 GiB runs. It also lowered the server’s readings: 692 MiB settled idle on the 4 GiB run against 757 MiB uncapped, and 1,224 MiB at start-up against 1,631 MiB. We do not know what the server would have done with that memory if it were free.
- Lower job concurrency. The FAQ lists “Lower the job concurrency for these jobs to 1” under lowering CPU and RAM use. We set Smart Search and Face Detection to 1 (default 2). On Oct 2 the machine-learning peak was 1,812 MiB against 1,880 MiB, the stack peak 3,363 MiB against 3,409 MiB, and the jobs took 36.2 s against 42.0 s (
immich-ml-1job.json,immich.json). On Oct 1 the same comparison went the other way: 1,837 MiB against 1,727 MiB for machine learning (lab notes, section 7). One run each, so treat a difference of about 100 MiB as noise. In our tests, concurrency 1 did not lower RAM measurably. - Use a smaller face model or move machine learning elsewhere. The FAQ says to change the facial recognition model to
buffalo_s, “a smaller and faster model”, and to re-run Face Detection afterward. It also says you can run the machine-learning container on another machine. Not tested in our lab: both.
Our numbers against the official requirements
Immich’s requirements page says “RAM: Minimum 6GB, recommended 8GB”, and for “systems with only 4GB of RAM, Immich can be run with machine learning features disabled”. It also says: “if Docker resource limits are used, the Postgres database requires at least 2GB of RAM”.
| Official statement | What we measured | Match |
|---|---|---|
| 6 GB minimum, 8 GB recommended | Full stack: 3.3 GiB peak, 8 GB by our 1.5x rule | Our rule lands on the recommended size; the stack itself used about 60% of 6 GB |
| 4 GB works with machine learning disabled | No-ML stack: 2.1 GiB highest reading (at start-up), 1.5 GiB under load; 4 GB by our rule | Consistent with it; our limits are not a real 4 GB host |
| Postgres needs at least 2 GB under Docker limits | Postgres at a 640 MiB limit (418 MiB mean at idle, 426 MiB highest under load in the committed run) was killed on first start in 5 of 8 runs; at 512 MiB it was killed on first start on Oct 1 | Consistent with the doc: below 2 GB our database limit was unreliable. We did not test 2 GB, and our library is 20 photos |
Why our numbers are lower than the doc’s 6 GB:
- Different thing measured. The doc’s number is for a host. Ours is the sum of the four containers’ working sets. The operating system, Docker and file cache come on top, which is what our 1.5x factor stands in for.
- A small load. 20 photos with no faces and no video. A real first import adds face recognition on real faces, video transcoding and thousands of assets. We did not measure those, so the doc’s headroom may be justified by them.
- Defaults. Our run used Immich’s default models (
ViT-B-32__openaifor search,buffalo_lfor faces,PP-OCRv5_mobilefor text) and default job concurrency.
What we add to the doc: machine learning accounts for more than half of the load peak (the runs with and without it differ by 1,889 MiB of 3,409 MiB), and a 4 GiB budget with machine learning on cost a machine-learning worker in all five runs.
What we did not measure
- Libraries of thousands of photos, any video transcoding, and face recognition on real faces (our images contain none).
- A real 4 GB or 2 GB machine. We capped containers on a 16 GB host, so the operating system’s share is missing.
- The full stack with machine learning at 2 GiB.
- A lower
MACHINE_LEARNING_MODEL_TTL, thebuffalo_smodel, remote machine learning, hardware acceleration (the host has no GPU), ARM hosts. - Memory growth over days. Our longest watch was 10 minutes. The harness timelines end about 3.5 minutes after healthy, so a change later than that is not in them.
- Other memory splits for the 4 GiB run. We ran one.
- Other apps on the same host. We measured Immich alone. A second app adds its own figure: Vaultwarden behind Caddy idled at 24 MiB and peaked at 83 MiB in a separate lab run. We did not run the two stacks together.
Gotchas, upgrade and backup
- A killed worker looks healthy. In the 4 GiB runs the machine-learning container never stopped and its restart count stayed 0, yet
OOMKilledwastrueand a worker had been killed. Checkdocker logs immich_machine_learningforSIGKILL, not onlydocker ps. The FAQ says SIGKILL or error code 137 “most likely means the service is running out of memory”. OOMKilledresets when a container restarts. In the second failed 2 GiB attempt the server was killed for memory (Docker’s event log showedoom immich_server, exit code 137) and restarted, and the end-of-run check then reportedOOMKilled=false. The restart count (1) was the evidence. Read both fields; our lab now records both.- First start needs more memory than steady state. The failed 2 GiB attempts died on first start, before any upload (on Oct 1 during the geodata import; on Oct 2 the database was killed in 5 of 8 runs). Our server’s start-up peak was 1.2 to 1.6 GiB against 0.7 GiB settled. If you cap memory, size the limits for the first two minutes, not for idle.
mem_limitis a hard limit. Put it only on the services you mean to cap, and keep the sum under the host’s RAM with space left for the operating system.- Upgrade and backup are covered, with the commands we ran, in Update safely and Back up and restore the database in the setup guide. Re-check RAM after an upgrade: these numbers are v3.2.4 only.
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.
- 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
- Setup, the verified file and the update and backup steps: the full-stack setup guide.
- 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: Immich’s requirements, FAQ and environment variables docs, all read Oct 1, 2026.
Update log
- The downloadable .env files no longer carry the lab's database password: DB_PASSWORD is the placeholder CHANGE_ME_DB_PASSWORD. The lab ran with the old value; measurements are unchanged.
- Linked the new Vaultwarden Docker Compose page from the list of things we did not measure (other apps on the same host).
- Simplified the page: two variants moved into the summary table.
- Re-tested all five runs under the new idle-RAM method (we now wait for memory to settle before measuring idle). Full-stack idle is 1,364 MiB, was 2,162; no-ML idle 1,120, was 1,919. Full-stack peaks moved by 3% or less (3,409 MiB, was 3,324); the no-ML peak under load is 1,520 MiB (was 1,662) and the 2 GiB run's 1,522 MiB (was 1,730). The 2 GiB no-ML run now fails on first start in 5 of 8 attempts; the 4 GiB run lost the ML worker in all 5 runs.
- First published. Five lab runs on Immich v3.2.4: full stack, machine learning off, 4 GiB and 2 GiB memory caps, and ML job concurrency 1.