Apps

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.

Edited by Simon Carter ·

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.
Per-container figures for Immich
ContainerIdle RAMPeak RAM (under load)Image, compressed (download)Image, unpacked (disk)
database439 MiB449 MiB205 MB989 MB
immich-machine-learning163 MiB1.8 GiB342 MB1.4 GB
immich-server757 MiB1.1 GiB617 MB2.5 GB
redis5 MiB6 MiB44 MB167 MB
Total1.3 GiB3.3 GiB1.2 GB5.0 GB
Total RAM after the stack became healthy (10 s averages)
Total RAM across all containers in 10 s averages, from the moment the stack was healthy until the end of the idle window (210 s). Highest 10 s average 2.1 GiB at 70 s, last 1.3 GiB at 210 s. Dashed line: the stack counted as settled at 176 s.

0 s to 210 s after healthy; the line spans 0 to 2.1 GiB. Dashed line: settled.

Timeline as a table
Total RAM, 10 s average ending at each time, every 30 s after healthy
Seconds after healthyTotal RAM
302.1 GiB
602.1 GiB
902.1 GiB
1201.7 GiB
1501.3 GiB
1801.3 GiB
2101.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.

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.

compose.yamlVerified 2026-10-02 · Immich (no machine learning) v3.2.4Download compose.yaml for Immich (no machine learning)
# 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.
Per-container figures for Immich (4 GiB cap)
ContainerIdle RAMPeak RAM (under load)Image, compressed (download)Image, unpacked (disk)
database389 MiB400 MiB205 MB989 MB
immich-machine-learning162 MiB1.6 GiB342 MB1.4 GB
immich-server692 MiB1.1 GiB617 MB2.5 GB
redis5 MiB6 MiB44 MB167 MB
Total1.2 GiB3.1 GiB1.2 GB5.0 GB
Total RAM after the stack became healthy (10 s averages)
Total RAM across all containers in 10 s averages, from the moment the stack was healthy until the end of the idle window (300 s). Highest 10 s average 1.7 GiB at 20 s, last 1.2 GiB at 300 s. Dashed line: the stack counted as settled at 264 s.

0 s to 300 s after healthy; the line spans 0 to 1.7 GiB. Dashed line: settled.

Timeline as a table
Total RAM, 10 s average ending at each time, every 30 s after healthy
Seconds after healthyTotal RAM
301.6 GiB
601.6 GiB
901.7 GiB
1201.5 GiB
1501.3 GiB
1801.3 GiB
2101.2 GiB
2401.2 GiB
2701.2 GiB
3001.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.

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.
Per-container figures for Immich (no machine learning, 2 GiB cap)
ContainerIdle RAMPeak RAM (under load)Image, compressed (download)Image, unpacked (disk)
database418 MiB426 MiB205 MB989 MB
immich-server716 MiB1.1 GiB617 MB2.5 GB
redis4 MiB6 MiB44 MB167 MB
Total1.1 GiB1.5 GiB866 MB3.6 GB
Total RAM after the stack became healthy (10 s averages)
Total RAM across all containers in 10 s averages, from the moment the stack was healthy until the end of the idle window (210 s). Highest 10 s average 1.5 GiB at 20 s, last 1.1 GiB at 210 s. Dashed line: the stack counted as settled at 175 s.

0 s to 210 s after healthy; the line spans 0 to 1.5 GiB. Dashed line: settled.

Timeline as a table
Total RAM, 10 s average ending at each time, every 30 s after healthy
Seconds after healthyTotal RAM
301.5 GiB
601.5 GiB
901.5 GiB
1201.3 GiB
1501.1 GiB
1801.1 GiB
2101.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.

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.

What reduces it, and what we saw:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

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

Gotchas, upgrade and backup

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.

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.