Self-hostingWritten by GLWS18 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

What Immich needs

Immich is a self-hosted photo and video library with mobile apps, face and object search and sharing. It stores your originals, makes thumbnails and transcodes video, and its machine-learning features need real resources. The project's documentation recommends several gigabytes of memory, so use a plan with 8 GB or more, such as an AI and development plan, and several vCPUs. Search and face recognition indexing run on the CPU on our plans, so the first import of a large library takes a long time.

Storage is the main cost: your whole library plus thumbnails and transcoded videos. Check the project's current requirements before you choose a plan.

Both systems
free -h
nproc
df -h /
sudo docker compose version
A photo library is irreplaceable. Treat Immich as a viewer on top of your originals, and keep a separate backup, as described below.

Download the official files

Use the Compose file and example environment published with the latest release. Create a folder, download both files and rename the example file to .env. Do not copy an old file from a blog post, because the services change between versions.

Both systems
mkdir -p ~/immich && cd ~/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
ls -l

Edit the settings

Open .env and set the upload location, which is where originals are stored, a database location, your time zone, a random database password, and the Immich version. Pin the version to a specific release for production rather than using release, so updates happen when you choose. Keep the file private because it holds the database password.

Both systems
cd ~/immich
chmod 600 .env
sudo mkdir -p /srv/immich/library /srv/immich/postgres
sed -i "s|^UPLOAD_LOCATION=.*|UPLOAD_LOCATION=/srv/immich/library|;s|^DB_DATA_LOCATION=.*|DB_DATA_LOCATION=/srv/immich/postgres|;s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -hex 16)|" .env
nano .env
grep -E "UPLOAD_LOCATION|DB_DATA_LOCATION|TZ|IMMICH_VERSION" .env
Keep the database on local disk, not on a network share, because PostgreSQL does not work reliably there.

Keep it private, then start it

The official file publishes the web interface on port 2283 for every address. Change the port mapping so that it listens only on localhost, then reach it through a reverse proxy or an SSH tunnel, as the earlier guides describe. Check the port line in the server service before starting.

Both systems
cd ~/immich
grep -n "2283" docker-compose.yml
sed -i 's|"2283:2283"|"127.0.0.1:2283:2283"|' docker-compose.yml
grep -n "2283" docker-compose.yml
sudo docker compose up -d
sudo docker compose ps
Check what listens
sudo ss -lntp | grep 2283
If the port line looks different in your version, edit it by hand so that the left side begins with 127.0.0.1:. Check the project's notes for the current file layout.

Create the admin account and publish it

Open the interface through an SSH tunnel to port 2283, and create the first account, which becomes the administrator. Then publish it behind HTTPS with Caddy or Nginx. Photo uploads are large, so raise the upload size limit and the timeouts in Nginx, and enable WebSocket headers. Install the mobile app, enter your server address and turn on background backup.

On your computer
ssh -L 2283:127.0.0.1:2283 deploy@YOUR_SERVER
Nginx location settings
client_max_body_size 50000M;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;

Control the load

The first import and the machine-learning jobs use a lot of CPU, which can make a small server unusable. In the administration settings, lower the concurrency of the background jobs, and run the heavy jobs while you are not using the server. The machine-learning container can be limited or removed if you do not need smart search and face recognition. Watch memory and CPU while it runs.

Both systems
cd ~/immich
sudo docker stats --no-stream
free -h
uptime

Back up the photos and the database

Back up both parts. The originals in UPLOAD_LOCATION are what you cannot recreate. The database holds albums, people, tags and settings, and the library without it loses its organisation. Immich creates database dumps on a schedule in a backups folder inside the library location, and the documentation describes how to restore them. Copy the whole library folder, including that backups folder, to another machine, with a tool such as restic as in the backup guide.

Follow the 3-2-1 rule: three copies, on two kinds of storage, one of them off site. A copy on the same server protects only against deletion mistakes.

Stop the server and machine-learning services before pairing a database dump with a library copy. Do not copy the live PostgreSQL data directory. Protect .env and the application files off-server alongside these archives. The commands use the default PostgreSQL username and database; adjust them if you changed the official environment file.

Both systems: confirm the dumps exist and are recent
ls -lh /srv/immich/library/backups/ | tail -n 5
sudo du -sh /srv/immich/library
Both systems: take a manual database dump as well
cd ~/immich
set -o pipefail
umask 077
sudo docker compose stop immich-server immich-machine-learning
sudo docker compose exec -T database pg_dump --clean --if-exists --username=postgres --dbname=immich | gzip > immich-db-$(date +%F).sql.gz
sudo tar czf immich-files-$(date +%F).tar.gz -C /srv/immich/library .
sudo chmod 600 immich-db-*.sql.gz immich-files-*.tar.gz
gzip -t immich-db-$(date +%F).sql.gz
sudo docker compose start immich-server immich-machine-learning
Capture application files and a consistent database backup. Keep an encrypted copy off the VPS, restore it separately, and verify the restored data before relying on the backup.
Capture application files and a consistent database backup. Keep an encrypted copy off the VPS, restore it separately, and verify the restored data before relying on the backup.
The container name may differ between versions. Run docker ps to find the Postgres container, and read the current restore instructions in the Immich documentation before you rely on a backup.

Update deliberately

Immich releases often and sometimes includes breaking changes, which are listed in the release notes. Read them before each update, take a backup, change the version in .env, pull and restart. Check the logs and the interface, and test that uploads and search work. Do not update right before a trip when you cannot fix a problem.

Both systems
cd ~/immich
nano .env
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 40 immich-server

Test a restore

Use a disposable recovery instance with the same Immich version, Compose configuration, .env and library paths as the backup. Confirm the database name, archive and destination before proceeding. These commands replace the application database contents and clear the upload library; preserve existing data first if this is not an empty recovery target.

Start PostgreSQL alone, replay the database, restore the whole matching library, then start the application. Sign in, locate a known uploaded image and download its original. Compare its checksum with the original file; thumbnails alone do not prove recovery worked.

Restore the confirmed database and library archives
cd ~/immich
set -o pipefail
sudo docker compose stop immich-server immich-machine-learning
sudo docker compose up -d database
# Wait until PostgreSQL is ready before importing.
sudo docker compose exec -T database pg_isready --username=postgres
gunzip -c immich-db-YYYY-MM-DD.sql.gz | sudo docker compose exec -T database psql --set=ON_ERROR_STOP=on --username=postgres --dbname=immich
# Confirm this exact upload directory and archive before clearing it.
sudo find /srv/immich/library -mindepth 1 -maxdepth 1 -exec rm -rf {} +
sudo tar xzf immich-files-YYYY-MM-DD.tar.gz -C /srv/immich/library
sudo docker compose start immich-server immich-machine-learning
sudo docker compose logs --tail 40 immich-server
Immich library showing the uploaded Nano test image after restoration.
Actual Immich library after restoring its database and uploaded files. The Nano image is a test fixture; its downloaded original matched the pre-backup checksum.

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.