Immich is an open-source photo and video library that you run yourself. Its phone apps back up your camera roll, and it has albums, sharing, face recognition and search by description, such as "dog on a beach". We tested this guide with Immich v3.3.5, the latest release at the time, on Debian 13, Ubuntu 24.04 and AlmaLinux 10.
1. Check that your server is big enough
Immich needs at least 6GB of RAM and 2 CPU cores; 8GB and 4 cores are recommended. On our 6GB test server it used about 2.5GB at rest. With 4GB it runs only with machine learning turned off, which removes face recognition and search by description.
For disk, plan for your photos plus 10 to 20 percent for thumbnails, and about 10GB for the software, database and its backups.
Machine learning also needs a CPU that supports x86-64-v2. Every D4 Networks location does; to confirm:
$ /lib64/ld-linux-x86-64.so.2 --help | grep x86-64-v2The x86-64-v2 line should include supported. To pick a plan, see how much RAM and CPU your VPS needs.
2. Install Docker
On AlmaLinux, update the server and reboot before you install Docker:
$ sudo dnf upgrade -y $ sudo reboot
On our fresh AlmaLinux 10 test server, Docker would not start until it ran the newest kernel.
Then install Docker from Docker's own repository, as in our Docker guide; Immich warns that some distributions' own Docker packages can fail. Check the Compose plugin:
$ docker compose versionImmich needs docker compose, with a space; the older docker-compose is not supported.
3. Download Immich
Download the setup files from the latest release into their own folder:
$ mkdir ~/immich-app $ cd ~/immich-app $ curl -fsSL -o docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml $ curl -fsSL -o .env https://github.com/immich-app/immich/releases/latest/download/example.env
4. Set a password and keep Immich private
Before the first start, give the database a random password (Immich allows only letters and digits):
$ sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)/" .env $ chmod 600 .env
Then make Immich listen only on the server itself:
$ sed -i "s/- '2283:2283'/- '127.0.0.1:2283:2283'/" docker-compose.yml $ grep 2283 docker-compose.yml
The line should now read - '127.0.0.1:2283:2283'. Without this, anyone could reach Immich on port 2283, because ports opened by Docker skip ufw and firewalld rules. Caddy will be the only way in. Your photos go in ~/immich-app/library; the other .env settings can stay as they are.
5. Start Immich
$ docker compose up -d $ docker compose ps
The first start downloads about 4.6GB of images. When the four containers show Up, check that Immich answers:
$ curl http://127.0.0.1:2283/api/server/pingIt should print {"res":"pong"}. Then confirm it is not reachable from outside:
$ docker compose port immich-server 2283This must print 127.0.0.1:2283. If it shows 0.0.0.0:2283, step 4 did not apply: run docker compose down, fix the ports line in docker-compose.yml, and start again.
Create your account now, while Immich is still private, because the first account becomes the admin. On your own computer, open an SSH tunnel:
$ ssh -L 12283:127.0.0.1:2283 user@203.0.113.10With that session open, browse to http://127.0.0.1:12283, click Getting Started and create your account. Sign-up then closes, and you can close the tunnel; add other people yourself under Administration.
6. Add HTTPS with Caddy
Point a domain at your server, for example an A record for photos.example.com. If your DNS is at Cloudflare, set that record to DNS only (grey cloud); Cloudflare's proxy rejects uploads over 100MB on its Free and Pro plans. Then install Caddy from its official repository:
$ sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gpg $ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg $ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list $ sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list $ sudo apt update $ sudo apt install -y caddy
$ sudo dnf install -y dnf-plugins-core $ sudo dnf copr enable -y @caddy/caddy $ sudo dnf install -y caddy $ sudo systemctl enable --now caddy
Replace everything in /etc/caddy/Caddyfile with these lines, using your domain:
photos.example.com {
reverse_proxy 127.0.0.1:2283
}Check the file, then open the web ports and load the new config:
$ sudo caddy validate --config /etc/caddy/Caddyfile $ sudo ufw allow 80,443/tcp $ sudo systemctl reload caddy
$ sudo caddy validate --config /etc/caddy/Caddyfile $ sudo firewall-cmd --get-active-zones $ zone=public $ sudo firewall-cmd --permanent --zone="$zone" --add-service=http --add-service=https $ sudo firewall-cmd --reload $ sudo systemctl reload caddy
The check must print Valid configuration. For firewalld, replace public with the zone that --get-active-zones lists for your network interface.
Once the domain points to your server, Caddy gets a free HTTPS certificate by itself. In our tests it handled a 224MB video upload with no extra settings.
7. Connect your phone
Install the Immich app from the App Store, Google Play or the FUTO F-Droid repository. Enter https://photos.example.com as the server address, log in, and turn on backup. If uploads stall on Android, let Immich run without battery limits; on iPhone, turn on Background App Refresh for it.
8. Back up the right things
By default, Immich saves its database every night in ~/immich-app/library/backups, keeping the last 14. Those copies hold no photos and sit on the same server, so they are lost with it.
For a backup where the database matches the files, stop the Immich server and take a fresh database dump:
$ cd ~/immich-app $ docker compose stop immich-server $ (umask 077; set -o pipefail; docker compose exec -T database pg_dump --clean --if-exists --username=postgres --dbname=immich | gzip > db-dump.sql.gz) && gzip -t db-dump.sql.gz && zgrep -q 'PostgreSQL database dump complete' db-dump.sql.gz && echo 'Backup verified'
The parentheses keep the dump private from the moment it is created, and the checks catch a dump that failed or was cut short. Use it only if Backup verified appears.
Copy db-dump.sql.gz, the library folder, .env and docker-compose.yml to another server, for example with restic, then run docker compose start immich-server. Check now and then that the backups still run, and test a restore before you need one.
9. Upgrade Immich
Read the release notes on Immich's GitHub page and update the phone apps first. Take a snapshot, because Immich cannot go back to an older version. Then:
$ cd ~/immich-app $ docker compose pull && docker compose up -d
This keeps you on the main version in .env, version 3 at the time of writing. Moving to the next one means changing IMMICH_VERSION yourself, after reading its release notes, which may also ask for changes to the setup files.
10. When something goes wrong
- Read the server log with
docker compose logs immich-serverin~/immich-app. - A container that keeps restarting, or exit code 137, usually means memory ran out. Add swap, or turn off machine learning under Administration, Settings, Machine Learning Settings.
- If HTTPS does not load, check the domain, that ports 80 and 443 are open, and Caddy's log with
sudo journalctl -u caddy -n 50.
Where to go next
Keep the server patched and protected as in the first ten minutes on a new VPS. Immich's docs note that a public site always carries some risk from undiscovered bugs; if only your household uses it, you can keep it off the internet and reach it through a WireGuard VPN.