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 even when you never tag anything. You go run am for VPS wey you own, the original files go remain for your disk, and nobody go scan dem to sell you things. If you still dey compare am with the other obvious option, our PhotoPrism and Immich comparison put their RAM minimums, phone apps, and backup commands side by side.
The install na four containers from the project own Docker Compose file. That part fit take ten minutes. The remaining part na where wahala dey: the machine-learning container dey use plenty memory for small server, originals dey consume disk space fast, the mobile app need the exact server URL, and you need HTTPS in front of am before e leave your network. Immich dey release breaking changes often enough that careless docker compose pull fit make your server no start. Take these four things seriously and Immich go stable well. 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 problem for small VPS na ML container dey get OOM-killed. Give kernel swap space first make e get where e fit use when memory no reach.
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 don show one Swap: line of 4.0Gi now. This no go make ML fast, and na buffer e be, no be guarantee. But e go make the container much less likely to die halfway through indexing for 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 make e remain 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 wey go save you wahala. UPLOAD_LOCATION suppose point to your big disk. If you later attach data volume, set am to the mount path from the beginning, because to move am later mean say you go 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 you use only letters and digits for DB_PASSWORD, you go avoid one kind connection-string escaping bug.
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 a reverse proxy, and the full-URL rule
The mobile app really need HTTPS once e comot from your home network. Put reverse proxy for front of port 2283 and terminate TLS 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 for you and create proxy_pass http://127.0.0.1:2283; block. 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 wey put Codex and Claude Code behind one API. E bind to loopback on purpose and e only become reachable after the proxy terminate TLS for front of am. So change the default login before you point subdomain to am. No be every container suppose get public hostname, though. Admin-only tool like a self-hosted open-kritt security scanner better make e stay completely outside the proxy. You fit reach am through an SSH tunnel only for the rare times wey you open the UI. Some others no use the proxy because HTTP no be their protocol at all. a self-hosted RustDesk relay server na clear example. E listens on some raw TCP and UDP ports and e need firewall rules instead of subdomain. One proxy setting matter for Immich: raise the upload size limit because phone videos dey large. For nginx, na client_max_body_size 50000M; inside the server block. The default 1 MB rejects video uploads with 413 Request Entity Too Large. If your proxy also answers Let's Encrypt HTTP-01 challenges, make sure say /.well-known/immich still route go Immich, no be go the challenge handler.
The rule wey the app enforce be say endpoint must dey reachable for exactly the URL wey you type. The app go accept plain http://YOUR_SERVER_IP:2283. Its own login hint na http://your-server-ip:port. But anything wey cross the internet suppose use HTTPS. Wrong scheme, or direct IP wey you leave the port out, na wetin cause “the app cannot reach the server”. We cover am below as named failure.
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.
Imin Step 8: The upgrade discipline wey Immich need
Na this part dey separate Immich wey dey work well from the one wey don break. Immich dey release updates fast, and e no dey backport fixes or support downgrades. The floating v3 tag na officially supported way to follow releases, and Immich dey try keep breaking changes for major versions. But e go upgrade you anytime you pull, whether you don read the notes or not. If you do upgrade wey you no plan, you no fit roll am back. This same habit of pinning first, then reading the notes, dey useful for every long-running container for the server. Na why self-hosted KiroCrew agent dey pinned to one known-good tag instead of allowing am change by itself for the next restart. The discipline be:
- Pin one version. Keep
IMMICH_VERSIONset to one concrete tag likev3.0.2, no be the floatingv3wey always dey pull the newest v3.x. - Read the release notes every 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 complete the VectorChord migration, wey dem introduce back for v1.133, before e fit upgrade.
- Back up the database first (Step 9). Always do am, but do am twice carefully when the notes mention the database.
- Take the new compose file too.
IMMICH_VERSIONonly pins the server and ML images. The Postgres image dey pinned by digest insidedocker-compose.yml. So, if one version need newer database extension, e go release with new compose file. Download both release assets again, apply your.envvalues again, then upgrade. - Update your mobile clients first. The server only dey speak with its matching major version, and the app normally dey compatible with the current and previous major version. When the server newer pass the app, current releases (v3.3.1, checked 2026-10-10) dey show App update is available notice with an Update button for the phone. Immich recommend say you upgrade all mobile clients before the server.
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: Backup, database dump PLUS the original files, 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. This two-part setup no be special problem for Immich: self-hosted Chatwoot support desk need the same Postgres dump and uploads directory together, or the restored inbox go come back with every attachment missing. Copying the Postgres data directory as file tree fit look like shortcut around the dump step, but e no be usable backup. Na trap wey the full Immich backup and restore walkthrough dey explain together with the restore mistake wey leave you with empty timeline.
Use pg_dump inside the Postgres container to dump the database. Dump immich 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, including the whole /opt/immich/library tree and especially its library/, upload/, and profile/ subfolders. Use restic, rsync, or borg to copy am go another machine or object storage. Any tool wey schedule this job, whether na cron entry or systemd timer, need somewhere to report when e fail. A systemd OnFailure= unit wey point to your own ntfy push server fit send message go your phone the night the dump fail, instead make you discover am during restore. Do the database first, then the files, so the dump no go reference photo wey file backup never copy. 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 whose server never start before, using 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 VectorChord database. If you leave am out, 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 test before, you no get backup; you get hope.
Wahala wey fit happen, plus the strings wey you go see
ML container dey get OOM-killed. sudo docker compose logs immich-machine-learning dey stop suddenly, docker compose ps show say 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 this order: add swap (Step 1); give the VPS more RAM; or, if you truly no fit do that, open Administration → Settings → Machine Learning Settings and switch off the top-level machine learning toggle. This one go disable every ML feature at once. To switch off only Smart Search and Facial Recognition no dey enough, because OCR and the other model types still dey enabled by default. Your backups and albums go remain, but you go lose search-by-content. If you remove the immich-machine-learning service from the compose file, e get the same effect.
Immich server no gree start after upgrade. Postgres container fit dey run fine; na immich_server dey check the vector extension version when e start, and e refuse to start. The 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 extension version old pass the version wey your data upgrade reach. This almost always happen 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.
Mobile app no fit reach server. Login screen dey show connection error like Could not connect to server after you enter the URL. Common causes be say: you type http:// when the proxy only dey serve https://; you connect directly to the backend but leave the port out, so e try example.com (port 443) instead of example.com:2283; reverse proxy no dey forward /api; or proxy wey dey handle Let's Encrypt HTTP-01 challenges dey route /.well-known/immich go somewhere wey no be Immich. Fix am by entering the full https://photos.example.com URL and first confirm say e dey load for phone browser. If browser work but app no work, proxy dey remove the path or dey route /.well-known/immich wrongly. Another possibility be self-signed certificate, wey the app reject with handshake error unless you enable self-signed certificate support for its settings.
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 bigger 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 full, so clear space and restart the database container before you conclude say corruption happen.
FAQ
How much RAM and disk Immich need?
Immich official requirement na 6 GB RAM minimum and 8 GB recommended. For small library, 4 GB with swap na practical minimum. Configure swap either way, because machine-learning container na the part wey fit spike. For disk, plan for the full library size plus roughly 10–20% for generated thumbnails and previews. Keep am for local storage. Never put the Postgres data directory for network share. If you still dey decide wetin else to run, the guide about wetin to self-host for 2026 puts Immich footprint beside other services.
I fit run Immich without GPU?
Yes. The 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 large library fit take hours for background, but e no block backups or browsing. If your box too small for ML altogether, you fit turn machine learning off completely with the top-level toggle for admin settings and keep everything else.
How I fit upgrade Immich safely?
Pin IMMICH_VERSION to concrete tag like v3.0.2. Read the release notes before every upgrade, and back up the database first. Because Postgres image dey pinned inside docker-compose.yml instead of by IMMICH_VERSION, download both the compose file and example.env again from your target release, re-apply your values, then run docker compose pull && docker compose up -d. Never leave the version to float unattended. Immich no support downgrades, so upgrade wey you no read about beforehand no fit undo.
Wetin exactly I need back up?
Two things together: a pg_dump of the immich database and the complete UPLOAD_LOCATION originals directory. The database dey hold albums, faces, and asset-to-file mapping. The directory dey hold the actual photos, and restore need both of dem plus database image wey get compatible vector extension. Do the database dump first and file copy second. Test the restore for scratch box at least once. Backup wey you never test no be backup.
How I fit import my existing photo folder?
Mount the folder read-only inside the 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 in place and never modifies or deletes dem. The commonest mistake na to enter host path instead of container path, and this make the scan find nothing.