Docker & DevOps

Self-host Immich for photo backups with Docker Compose

Deploy Immich on a Linux VPS with Docker Compose, connect the mobile app for automatic photo uploads, and back up the library off-site.

IntermediateLinux server7 min read

Immich is a self-hosted photo and video backup server that gives you Google Photos-style features on your own hardware. To self-host Immich with Docker Compose you download the official compose file and .env from an Immich release, point the upload and database paths at your VPS disk, then run docker compose up -d.

This guide walks through the whole path: preparing the host, starting the stack, putting HTTPS in front of it, connecting the mobile app, and backing up the library so a dead disk does not cost you every photo you own.

Before you start

You need:

  • A Linux VPS with root access and at least 2 GB RAM. Immich runs PostgreSQL, Redis, the server and a machine-learning container, so give it room.
  • Docker Engine and the Compose plugin. If you have not installed them yet, follow Install Docker and Docker Compose on a Linux VPS.
  • A domain name pointing at your server's IP if you want HTTPS and mobile app uploads outside your home network. Replace immich.example.com and 203.0.113.10 below with your own values.
  • Disk space for the photo library. Uploads grow fast, so plan for a separate volume or a large partition.

Log in to your server and confirm Docker works:

Bash
docker --version
docker compose version

Note: You manage power, reinstall and the browser console for this VPS in your VPS provider's control panel. If you lock yourself out over SSH, the console there still gets you in.

Self-host Immich with Docker Compose

1. Create the project directory

Keep Immich in its own directory so the compose file, .env, library and database sit together.

Bash
sudo mkdir -p /opt/immich
sudo chown deploy:deploy /opt/immich
cd /opt/immich

2. Download the official compose file and env template

Immich ships a compose file and an example env file with every release. Always take them from the release you intend to run, because service names and images change between versions. Open the Immich releases page, copy the latest tag, and substitute it for vX.Y.Z:

Bash
curl -L -O https://github.com/immich-app/immich/releases/download/vX.Y.Z/docker-compose.yml
curl -L -o .env https://github.com/immich-app/immich/releases/download/vX.Y.Z/example.env

Check the compose file before you start anything:

Bash
grep -n "ports:" -A 2 docker-compose.yml
grep -n "image:" docker-compose.yml

The server container publishes port 2283 by default. If you plan to run a reverse proxy on the same host, you can leave it as is and point the proxy at 127.0.0.1:2283.

3. Edit the.env file

Open .env and set the paths and credentials. A minimal version looks like this:

INI
UPLOAD_LOCATION=./library
DB_DATA_LOCATION=./postgres
TZ=Etc/UTC
IMMICH_VERSION=release
DB_PASSWORD=replace-with-a-long-random-password
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

Generate a strong database password rather than typing one:

Bash
openssl rand -base64 32

Warning: Changing UPLOAD_LOCATION or DB_DATA_LOCATION after the first start makes Immich look at empty directories and appear to lose your photos. Decide the paths now, and if you must move them later, stop the stack and move the data first.

If your library lives on a separate disk, mount it and point UPLOAD_LOCATION at the mount point, for example /mnt/photos/immich. Make sure the deploy user can write there.

4. Start the stack

Bash
docker compose up -d
docker compose ps

You should see four containers: immich_server, immich_machine_learning, immich_redis and immich_postgres. The database takes a few seconds to initialise on the first run. Watch the server until it reports it is listening:

Bash
docker compose logs -f immich-server

Press Ctrl+C to stop following the log. Then open http://203.0.113.10:2283 in a browser and create the first administrator account. That first account becomes the admin, so create it immediately.

5. Add HTTPS

The mobile app and browsers both expect a real certificate. The simplest route is a reverse proxy container. Follow Nginx Proxy Manager in Docker: HTTPS for many self-hosted apps and create a proxy host for immich.example.com forwarding to your server's IP on port 2283.

Two settings matter for photo uploads:

  • Enable Websockets Support so the upload progress and job queues work.
  • Raise the maximum body size. Nginx defaults to 1 MB, which fails on any real photo. Set client_max_body_size 50000m; in the advanced tab.

If you prefer plain Nginx, the equivalent block is:

Nginx
server {
    listen 443 ssl;
    server_name immich.example.com;

    client_max_body_size 50000m;

    location / {
        proxy_pass http://127.0.0.1:2283;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

6. Connect the mobile app

Install the Immich app from the App Store or Play Store, then enter your server URL as https://immich.example.com — no port number when you use HTTPS on 443. Log in with the admin account you created, and turn on automatic backup for the camera roll.

Tip: Let the first backup finish on Wi-Fi before you add a second device. The initial upload is the heaviest job the server will ever run.

Back up the Immich library

Immich stores two things you must protect: the upload folder (your original files and generated thumbnails) and the PostgreSQL database (albums, users, faces, metadata). Backing up only one of them gives you a broken restore.

Dump the database:

Bash
cd /opt/immich
docker compose exec -T database pg_dumpall --clean --if-exists --username=postgres > immich-db-$(date +%F).sql

Then copy the upload folder and the dump off the server. restic handles both in one snapshot and encrypts everything before it leaves the machine — see Encrypted off-site backups with restic: init, snapshot, restore and Sync VPS backups to Backblaze B2 or Google Drive with rclone.

Warning: Never restore a database dump into a running stack. Stop the containers with docker compose down, restore the upload folder and the dump, then start again. Restoring over live data can corrupt the database and leave you with an unusable library.

Check it works

  1. docker compose ps shows all four containers as running, and database as healthy.
  2. https://immich.example.com loads over a valid certificate with no browser warning.
  3. Upload one photo from the web interface and confirm it appears in the timeline.
  4. Upload one photo from the mobile app and confirm the same photo shows up on the web.
  5. Run the database dump command above and confirm the .sql file is not empty.

Troubleshooting

  • immich_server restarts in a loop with a database connection error — the server started before PostgreSQL finished initialising. Check docker compose logs database for errors, then docker compose restart immich-server. If it keeps happening, add a healthcheck-based dependency as described in Docker Compose depends_on and healthchecks.
  • Mobile app says it cannot reach the server — usually a certificate or URL problem. Use the full https:// URL, confirm the certificate is trusted by the phone, and check that port 443 is open in your firewall. A self-signed certificate will be rejected by the app.
  • Uploads fail with HTTP 413 or stop at a few percent — the reverse proxy body limit is too small. Set client_max_body_size 50000m; and reload the proxy.
  • permission denied when writing to the upload folder — the container user cannot write to the mounted path. Fix ownership on the host with sudo chown -R 1000:1000 /mnt/photos/immich and restart the stack.
  • The machine-learning container eats all the RAM — it loads models for face and object detection. Either give the VPS more memory or disable the container in the compose file if you do not need smart search.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.