Immich self-hosting: 6 GB RAM and safe upgrade tips
Know the honest RAM need, how to put port 2283 behind HTTPS, fix exit 137 memory kills, and restore when Immich v3 rejects a pgvecto.rs database.
Wetin you dey build
Immich na self-hosted photo and video backup service, and e be real replacement for Google Photos. E get phone app wey dey upload your camera roll for background, timeline, albums, face recognition, and machine-learning search wey fit find "beach" or person without you tagging anything. You go run am for VPS wey belong to you, the original files go remain for your disk, and nobody go scan dem to sell things to you. If you still dey compare am with the other obvious option, our PhotoPrism and Immich comparison put their RAM floors, phone apps, and backup commands side by side.
The install na four containers from the project's own Docker Compose file. That part fit take ten minutes. The rest of this guide na where wahala dey: the machine-learning container dey use plenty memory for small box, originals dey chop disk space quickly, the mobile app no accept plain-HTTP server, and Immich dey release breaking changes often enough that careless docker compose pull fit make your database no start again. Take these four things seriously and Immich go dey very stable. Ignore dem and you fit lose one whole weekend.
Prerequisites, and the real things wey fit cause wahala
- RAM: official docs talk say 6 GB na minimum and 8 GB dey recommended, so treat 4 GB plus swap as the absolute minimum. The
immich-serverand Postgres containers no dey use plenty resources. Theimmich-machine-learningcontainer na the one wey dey chop memory pass. E dey load CLIP and face-recognition models into RAM to build search indexes, and for 2 GB machine, kernel go kill am. Add swap even if you get 4 GB. - Disk: size am for your complete library, plus extra space. The system go copy your original files completely. Immich go also generate thumbnails and preview images, roughly 10–20% extra. 200 GB photo collection need 300 GB volume. Postgres small compared with this.
- CPU: any modern KVM VPS dey okay, but ML for CPU slow. Smart-search indexing for big import fit run for hours for background. This one normal; e no need GPU.
- A domain name wey point to the VPS. Mobile app strongly prefer HTTPS endpoint, and you need reverse proxy for front. This setup similar to self-hosted Nextcloud instance with Docker, TLS and backups, and Immich na the photo equivalent of that files server.
- Docker and the Compose plugin wey you don install. Use Docker Engine plus Compose v2 plugin from Docker own apt repository, exactly as we cover for our Docker Compose basics guide.
Step 1: Add swap before anything else
The most common Immich failure for small VPS na when OOM killer kill the ML container. First give the kernel swap space wey e fit use.
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h suppose show a Swap: line of 4.0Gi now. This no go make ML fast, but e go stop the container from dying halfway through indexing for a 4 GB machine.
Step 2: Gba official compose and env, use their own one, no be copy
Immich dey pin service versions and, importantly, its database image inside the files wey e release. No paste compose file from blog (including this one) as your source of truth. Download the release assets:
sudo mkdir -p /opt/immich && cd /opt/immich
sudo wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
sudo wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.envThese ones come from tagged release, so the image references dey match. The compose file define four services, and e good make you know wetin each one dey do before you touch anything:
immich-server(ghcr.io/immich-app/immich-server, containerimmich_server), the API and web UI, dey listen on port2283. E mount your uploads for/data.immich-machine-learning(ghcr.io/immich-app/immich-machine-learning, containerimmich_machine_learning), CLIP search and face recognition. E cache downloaded models inside amodel-cachevolume. Na this one dey use plenty memory.database(containerimmich_postgres), Postgres with the VectorChord vector extension, wey dey power similarity search. Compose file pin the image tag by digest directly, for exampleghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:.... Older setups usepgvecto.rs; Immich remove support for am for v3.0, so anything wey you install today na VectorChord. No ever edit this tag by hand.redis(containerimmich_redis), a Valkey/Redis instance for job queues.
Step 3: Configure .env, wey your photos and database dey live
Open .env and set four things. Everything wey dey below the marked line stay as e be.
# Where original uploads are stored on the host
UPLOAD_LOCATION=/opt/immich/library
# Where the Postgres data lives. NEVER put this on an NFS/network share.
DB_DATA_LOCATION=/opt/immich/postgres
# "v3" is a floating tag that tracks the latest v3.x. Pin a full tag like
# v3.0.2 instead — then you upgrade on purpose, not by surprise.
IMMICH_VERSION=v3.0.2
# Change this to a long random string. Letters and digits only.
DB_PASSWORD=REPLACE_WITH_A_LONG_RANDOM_STRING
# Set your timezone so timestamps and "on this day" line up
TZ=Europe/London
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immichTwo rules fit save you plenty wahala. UPLOAD_LOCATION suppose point to your big disk. If you go attach data volume later, set am to the mount path from the beginning, because to move am afterward mean say you must move thumbnails and update asset paths. And DB_DATA_LOCATION must dey for local disk: Postgres dey corrupt if e dey on NFS or SMB share, and the docs talk am plainly. If na only letters and digits you use for DB_PASSWORD, you go avoid one kind connection-string escaping bugs.
Step 4: First run and how to create the admin user
cd /opt/immich
sudo docker compose up -d
sudo docker compose psCorrect result na four containers, and all of dem running before dem eventually healthy:
NAME STATUS
immich_machine_learning Up (healthy)
immich_postgres Up (healthy)
immich_redis Up (healthy)
immich_server Up (healthy)The first up go pull several gigabytes of images, so give am time. Use sudo docker compose logs -f immich-server monitor the progress; once e ready, the server go log say e dey listen on port 2283. Now open http://YOUR_SERVER_IP:2283 for browser. The first time you visit, Getting Started wizard go show. The first account wey you create na the admin. Set strong password; this account get server settings, user management, and the ML configuration wey you go need later.
Step 5: Mobile app and background backup
Install "Immich" from App Store or Play Store. For the login screen, e go ask for Server Endpoint URL. Enter the complete URL together with the scheme, like https://photos.example.com (the app go add /api by itself). Log in with the account wey you just create, then open the app Backup screen, choose the albums wey you want protect (normally Camera and Screenshots), and enable Background backup. iOS dey limit background backup with throttling. Foreground uploads always dey run. Background uploads go happen when the OS allow am.
Na for this point exactly plenty people dey stuck, so read Step 6 before you start struggle with the app.
Step 6: HTTPS through reverse proxy, and the full-URL rule
The mobile app really want HTTPS. Put a reverse proxy in front of port 2283 and terminate TLS for there. If you already dey run several containers, Traefik with automatic TLS for multiple Docker apps na the neatest option. One label block go route photos.example.com go the immich-server container and fetch the certificate for you. If you prefer nginx, the Let's Encrypt with Certbot and nginx guide go get certificate and one proxy_pass http://127.0.0.1:2283; block for you. Once that proxy dey available, adding the next service mostly na to create new subdomain. Na so media front end like Halcyon, the 90s video store skin for Jellyfin fit sit beside Immich for the same box. The same thing apply to a self-hosted HarnessRouter putting Codex and Claude Code behind one API. E bind to loopback on purpose, and e only become reachable after the proxy terminate TLS in front of am. So change the default login before you point subdomain to am. But no be every container suppose get public hostname. Admin-only tool like a self-hosted open-kritt security scanner better stay completely outside the proxy. You fit reach the UI through SSH tunnel only for the rare times wey you need am. Some others skip the proxy because HTTP no be their protocol at all. a self-hosted RustDesk relay server na clear example. E dey listen on some raw TCP and UDP ports, and e need firewall rules instead of subdomain. One proxy setting matter for Immich: increase the upload size limit because phone videos big. For nginx, na client_max_body_size 50000M; inside the server block. The default 1 MB dey reject video uploads with 413 Request Entity Too Large.
The rule wey the app enforce be say: endpoint must dey reachable and, for practical use, e must be HTTPS. http:// endpoints, or direct IP address wey no get port, na where "the app cannot reach the server" message dey come from. The named failure below cover this case.
Step 7: External libraries vs uploads, how to import existing photo tree
Two ways dey to put photos inside Immich, and dem no be the same thing.
- Uploads na assets wey Immich dey own. The app or web uploader dey copy the file go
UPLOAD_LOCATION. Immich fit rename, move, and delete dem. - External libraries na read-only imports of files wey already dey inside folder for your server, old
Picturestree, or NAS export. Immich indexes dem for where dem dey and shows dem for timeline, but e never modifies or deletes the original files.
To import existing tree, mount am read-only inside the server container. Edit docker-compose.yml under immich-server: and add one volume:
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
- /srv/photos:/mnt/media/photos:roThe :ro make sure say Immich no fit ever touch the original files. Recreate the container with sudo docker compose up -d. Then for the web UI, go your avatar → Administration → External Libraries → Create Library, choose the user wey dey own am, click Add under Folders, and enter the container path, /mnt/media/photos, no be the host path /srv/photos. Click Scan. To use the host path instead of the container path na the number-one external-library mistake; the scan no go find anything and e go report zero assets.
Step 8: The upgrade discipline wey Immich demand
Na this part separate working Immich from broken one. Immich dey release updates fast, and e no dey backport fixes or support downgrades. If you blindly follow the floating v3 tag, e go eventually break your database. This same pin-then-read-the-notes habit make sense for every container wey go live long for the server. Na why dem pin self-hosted KiroCrew agent to one known-good tag instead of allowing am change by itself for the next restart. The discipline be this:
- Pin one version. Keep
IMMICH_VERSIONset to one concrete tag likev3.0.2, no be the floatingv3wey always pull the newest v3.x. - Read the release notes every single time before you upgrade. Dem dey mention breaking changes there, especially database or vector-extension changes. The v3.0 release na clear example: e remove pgvecto.rs completely. So anybody wey still dey use the old extension first need finish the VectorChord migration (we introduce am since v1.133) before dem fit upgrade.
- Back up the database first (Step 9). Always do am, but do am twice as carefully when the notes mention the database.
- Take the new compose file too.
IMMICH_VERSIONonly pin the server and ML images. The Postgres image get pin by digest insidedocker-compose.yml. So, if one version need newer database extension, e go release new compose file. Download both release assets again, put your.envvalues back, then upgrade. - Update your mobile clients around the same time. The server only dey communicate with its matching major version, and the app support the current and previous major version. If server don move ahead of the app, phone go show
Your app major version is not compatible with the server!until you update am. So e safer to update the app first.
The actual commands, after you don put the new files in place:
cd /opt/immich
sudo docker compose pull
sudo docker compose up -d
sudo docker image pruneStep 9: Backups, database dump PLUS the originals, and test am
Backup for Immich na two things, and if one no dey, the other one useless. The database dey hold album structure, faces, search indexes, and the map from asset to file. The originals directory dey hold the actual photos. If you restore one without the other, you go get either photos without organisation or empty shell wey dey point to missing files.
Dump the database with pg_dump from inside the Postgres container. Use the immich database specifically, no be the whole cluster:
sudo docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres | gzip > /opt/immich/immich-db-$(date +%F).sql.gzThen back up UPLOAD_LOCATION, the complete /opt/immich/library tree, especially its library/, upload/, and profile/ subfolders, with restic, rsync, or borg to another machine or object storage. Do the database first and the files second, so the dump no go reference photo wey file backup never copy yet. Back up external libraries separately from their real source; Immich no own dem.
Now na the part wey everybody dey skip: test the restore. Restore must run against a fresh stack wey its server never start before, on a Postgres image wey vector extension compatible with the dump. Na exactly why you no suppose improvise the DB image tag. For a scratch box with the same compose and .env, wipe any old state, bring up only the database, then load the dump:
cd /opt/immich
sudo docker compose down -v
sudo docker compose pull
sudo docker compose create
sudo docker start immich_postgres
sleep 10
gunzip --stdout immich-db-2026-07-15.sql.gz |
sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" |
sudo docker exec -i immich_postgres psql --dbname=immich --username=postgres --single-transaction --set ON_ERROR_STOP=on
sudo docker compose up -dThe sed rewrite of search_path no be optional for a VectorChord database. If you leave am out, the restore go abort halfway. When the stack come back up with your originals in place, open the web UI. If your photos and albums dey there, your backup dey work. If you never run this before, you no get backup; na hope you get.
Failure modes, wit the strings wey you go see
The ML container dey get OOM-killed. sudo docker compose logs immich-machine-learning dey stop suddenly, docker compose ps dey show say e Restarting, and the exit code na 137. sudo dmesg | grep -i oom confirm am: Out of memory: Killed process ... (python3). Search and face jobs go then hang. The cause na RAM wey no reach for the models. Fixes, in order: add swap (Step 1); give the VPS more RAM; or, if you truly no fit, disable ML for Administration → Settings → Machine Learning Settings by turning off Smart Search and Facial Recognition. You go keep backups and albums, but you go lose search-by-content. Removing the immich-machine-learning service from the compose file get the same effect.
Postgres no gree start after upgrade. The server log dey repeat line like The database currently has VectorChord 0.5.3 activated, but the Postgres instance only has 0.4.2 available. This most likely means the extension was downgraded., or, for older stacks, The pgvecto.rs extension is not available in this Postgres instance.. The cause na database image wey get extension version older than the version wey your data upgrade reach. Almost always, na because person edit the image tag by hand or restore newer dump onto older image. The fix na to use the matching Postgres image, take the compose file from the release wey match your database, no downgrade, and restore only onto compatible image.
The mobile app no fit reach the server. The login screen dey show connection error / Server is not reachable after you enter the URL. Three things fit cause am: you type http:// when the proxy dey serve only https://; you connect directly to the backend but leave the port out, so e try example.com (port 443) instead of example.com:2283; or the reverse proxy no dey forward /api. Fix am by entering the full https://photos.example.com URL and first confirm say e loads for phone browser. If the browser work but the app no work, the proxy fit dey remove the path or the certificate fit be self-signed; the app rejects certificates wey e no trust.
Disk finish during import. Uploads go start fail, thumbnails go blank, and logs go show ENOSPC: no space left on device or, from Postgres, could not extend file ... No space left on device. df -h go show say the UPLOAD_LOCATION volume don reach 100%. Na why you suppose size disk before you import big library. To recover, attach larger volume, stop the stack, move UPLOAD_LOCATION go there, update .env, then start again. You fit also expand the existing disk if your provider allow am. Postgres fit wedge when disk fill up, so clear space and restart the database container before you assume say corruption happen.
FAQ
How much RAM and disk does Immich need?
Immich official requirement na 6 GB RAM minimum and 8 GB recommended. 4 GB with swap na practical minimum for small library. Configure swap either way, because machine-learning container na the part wey dey spike. For disk, plan for your full library size plus about 10–20% for generated thumbnails and previews on local storage. Never put Postgres data directory for network share. If you still dey decide wetin else to run, the guide about wetin to self-host for 2026 put Immich footprint beside other services.
Can I run Immich without a GPU?
Yes. Machine-learning container dey run well on CPU. GPU only make smart-search indexing faster and, with the correct image variant, video transcoding faster. For CPU, initial index of big library fit take hours for background, but e no dey block backups or browsing. If your box too small for ML completely, you fit disable Smart Search and Facial Recognition for admin settings and keep everything else.
How do I safely upgrade Immich?
Pin IMMICH_VERSION to one concrete tag like v3.0.2. Read release notes before every upgrade, and back up database first. Because Postgres image dey pinned inside docker-compose.yml instead of by IMMICH_VERSION, download both compose file and example.env again from your target release and apply your values again, then run docker compose pull && docker compose up -d. Never leave the version to float without supervision. Immich releases breaking changes and e no support downgrades.
What exactly do I back up?
Two things together: a pg_dump of the immich database and the complete UPLOAD_LOCATION originals directory. Database dey hold albums, faces, and asset-to-file mapping. The directory dey hold the actual photos, and restore need both, plus database image wey get compatible vector extension. Do database dump first and file copy second. Test the restore for scratch box at least once. Backup wey nobody test no be backup.
How do I import my existing photo folder?
Mount the folder as read-only inside immich-server container as extra volume, for example - /srv/photos:/mnt/media/photos:ro. Recreate the container. Then for Administration → External Libraries, create library and add the container path /mnt/media/photos. Immich indexes the files for where dem dey and never modify or delete dem. The commonest mistake na to enter host path instead of container path, and that one make scan find nothing.