Caption: Immich gives phones, browsers, background workers, machine learning jobs, PostgreSQL, Redis, and durable media storage one private home.
Introduction
Immich is an open-source photo and video management platform built for people who want the convenience of modern cloud photo apps without handing their personal library to a third-party service. It provides mobile auto backup, browser uploads, albums, search, sharing, background transcoding, thumbnails, face detection, metadata handling, and administrative controls through a web interface and native mobile apps.
Self-hosting Immich is especially attractive if your photos include family memories, client work, private travel records, scanned documents, or a large historical archive that should stay inside your own infrastructure. You decide where original files live, how backups are encrypted, which users can access the library, when upgrades happen, and how much storage the system can consume. The tradeoff is responsibility: Immich stores critical metadata in PostgreSQL and does not rebuild the application state just by scanning a folder of images, so database backups are as important as copying the original media.
This guide installs Immich on Ubuntu 24.04 LTS using Docker Compose, which the project recommends for production. The deployment keeps Immich behind Nginx, serves it at https://immich.example.com, stores uploads on a dedicated local path, keeps PostgreSQL on local SSD storage, and includes verification steps for uploads, mobile sync, background jobs, and backups. By the end, you will have a draft-ready production baseline for a private photo and video backup service.
Why Choose Immich?
- Private photo backup: Keep phone and browser uploads on your own server instead of a hosted consumer account.
- Modern user experience: Immich offers mobile apps, timeline browsing, albums, favorites, sharing, and browser access.
- Open-source ownership: The code, containers, and deployment files are public, inspectable, and runnable on your own VPS or homelab.
- Docker-first operations: Official Docker Compose files make the core stack repeatable across Ubuntu servers.
- Machine learning features: Optional ML services can generate smart search and recognition features when hardware allows.
- Clear backup model: Immich documents which folders and database dumps matter for a complete recovery plan.
- Family-friendly administration: Multiple users can back up phones to one server while administrators control storage and access.
Immich is not just a file browser pointed at a photo folder. It is an application with a database, job queues, generated thumbnails, encoded video, and upload state. Treat it like an important database-backed service, not like a disposable gallery container.
Prerequisites
Hardware Recommendations:
- 2 CPU cores minimum, 4 CPU cores recommended for smoother uploads, thumbnails, and video work
- 6 GB RAM minimum, 8 GB RAM recommended by the Immich documentation
- Local SSD storage for PostgreSQL data; do not place the database on an NFS, SMB, or other network share
- Enough media storage for originals plus generated content; plan for thumbnails and transcoded video to add roughly 10-20 percent overhead
- A 64-bit Linux server or VM; Docker inside LXC can work for advanced users but is not the recommended path
- Off-server backup storage such as another VPS, a NAS snapshot target, object storage, or encrypted backup repository
Software and Accounts:
- Ubuntu 24.04 LTS server with sudo access
- A domain such as
immich.example.com - DNS
AorAAAArecord pointing the hostname to your server - Docker Engine with the Docker Compose plugin
- Nginx, Certbot, curl, wget, OpenSSL, ufw, gzip, rsync, and jq
- A password manager for generated database credentials and admin recovery notes
- Android or iOS Immich app if you want mobile camera roll backup
Security Notes:
- Publish Immich only through HTTPS. Mobile photo backup sends private data and should never run over plain HTTP on the public internet.
- Bind the Immich container to
127.0.0.1:2283and let Nginx handle ports80and443. - Keep
.envprivate because it contains the PostgreSQL password and deployment settings. - Back up both the database and
UPLOAD_LOCATION. Database dumps alone do not contain photos or videos. - Pin the Immich version once the service is stable, then review release notes before upgrades.
- Give each family member or user their own account instead of sharing one administrator login.
Start by updating the server and opening only the ports you need:
sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl wget gnupg openssl ufw nginx certbot python3-certbot-nginx gzip rsync jq
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
Installation Guide
This deployment stores working files in /opt/immich. The official Compose file runs Immich services, PostgreSQL, Redis, and machine learning components. Nginx terminates HTTPS and forwards requests to the local Immich server port.
1. Install Docker Engine
Install Docker from Docker's convenience script or from the official apt repository. The important detail for Immich is that the modern docker compose plugin is available:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker
docker --version
docker compose version
Immich does not support the old docker-compose Python command. If docker compose version fails, fix Docker before continuing.
2. Create the Deployment Directory
Create a private deployment directory and separate folders for media, PostgreSQL, and local backup staging:
sudo mkdir -p /opt/immich
sudo chown -R "$USER":"$USER" /opt/immich
chmod 700 /opt/immich
cd /opt/immich
mkdir -p library postgres backups
Use storage that supports Unix ownership and permissions. The Immich documentation specifically warns against placing DB_DATA_LOCATION on a network share because PostgreSQL stability and performance are critical.
3. Download the Official Compose Files
Download the latest release Compose file and example environment file directly from the Immich GitHub releases:
cd /opt/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
chmod 600 .env
Keep these files under version-aware operational control. For a personal server, a private Git repository or encrypted configuration backup is enough. Do not commit .env to a public repository.
4. Configure .env
Edit .env so uploads and PostgreSQL data live where you expect, the timezone matches your users, and the database password is unique:
cd /opt/immich
export IMMICH_DB_PASSWORD="$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9' | head -c 32)"
cp .env ".env.$(date +%F-%H%M).bak"
python3 - <<'PY'
from pathlib import Path
import os
env_path = Path(".env")
text = env_path.read_text()
replacements = {
"UPLOAD_LOCATION=./library": "UPLOAD_LOCATION=/opt/immich/library",
"DB_DATA_LOCATION=./postgres": "DB_DATA_LOCATION=/opt/immich/postgres",
"# TZ=Etc/UTC": "TZ=Etc/UTC",
"DB_PASSWORD=postgres": f"DB_PASSWORD={os.environ['IMMICH_DB_PASSWORD']}",
}
for old, new in replacements.items():
text = text.replace(old, new)
env_path.write_text(text)
PY
grep -E '^(UPLOAD_LOCATION|DB_DATA_LOCATION|TZ|IMMICH_VERSION|DB_USERNAME|DB_DATABASE_NAME)=' .env
The official example uses IMMICH_VERSION=v3 at the time of writing. That is fine for initial testing, but production operators should pin a specific release tag after the service is verified so automatic pulls do not surprise you during a restart.
Caption: Nginx handles public HTTPS while the Immich containers, Redis, PostgreSQL, ML workers, and local media folders remain on the private side of the host.
5. Start Immich
Validate the Compose file, pull images, and start the stack:
cd /opt/immich
docker compose config
docker compose pull
docker compose up -d
docker compose ps
Watch the first boot logs while PostgreSQL initializes and the application starts:
docker compose logs --since=10m immich-server database redis
curl -i http://127.0.0.1:2283
If Docker reports can't set healthcheck.start_interval as feature require Docker Engine v25 or later, upgrade Docker Engine or comment the start_interval line in the database health check as noted by the Immich install docs.
6. Configure Nginx and HTTPS
Create /etc/nginx/sites-available/immich.example.com:
server {
listen 80;
listen [::]:80;
server_name immich.example.com;
client_max_body_size 50000M;
proxy_request_buffering off;
client_body_buffer_size 1024k;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location / {
proxy_pass http://127.0.0.1:2283;
proxy_http_version 1.1;
proxy_redirect off;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location = /.well-known/immich {
proxy_pass http://127.0.0.1:2283;
}
}
Enable the site and request a Let's Encrypt certificate:
sudo ln -s /etc/nginx/sites-available/immich.example.com /etc/nginx/sites-enabled/immich.example.com
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d immich.example.com
sudo certbot renew --dry-run
Immich must be served at the root of a domain or subdomain. Do not try to publish it from a sub-path such as /immich; the official reverse proxy docs warn that sub-path serving is unsupported.
Configuration
Open https://immich.example.com, create the first administrator account, and complete these tasks before inviting other users:
- Set the external URL if the administration settings expose it for your version. It should match
https://immich.example.com. - Create regular user accounts for phones and family members. Reserve the administrator account for administrative work.
- Review machine learning jobs under Administration. If your server has only 6 GB RAM, watch memory pressure during the first large import.
- Confirm storage paths from the host with
du -sh /opt/immich/library /opt/immich/postgres. - Enable automatic database backups in Administration > Settings > Backup and verify they appear under
UPLOAD_LOCATION/backups. - Document upgrade steps: back up first, read release notes, pull images, restart, and watch logs.
Useful operational commands:
cd /opt/immich
docker compose ps
docker compose logs --since=30m immich-server
docker compose logs --since=30m immich-machine-learning
docker compose exec database psql --username="${DB_USERNAME:-postgres}" --dbname="${DB_DATABASE_NAME:-immich}" -c 'select now();'
du -sh /opt/immich/library /opt/immich/postgres
When editing .env, restart the stack and inspect logs:
cd /opt/immich
docker compose up -d
docker compose logs --since=5m immich-server
Usage
Start with a small test library before allowing multiple phones to upload years of photos.
1. Upload a Browser Test Set
Use the web interface to upload ten mixed photos and one small video. Confirm that the timeline updates, thumbnails render, and the original files appear below /opt/immich/library or /opt/immich/library/upload depending on your version and storage settings.
Caption: Browser and mobile uploads create originals, metadata, thumbnails, encoded video, and searchable state that must be protected together.
Run a quick host-side check after the upload:
sudo find /opt/immich/library -maxdepth 3 -type f | wc -l
du -sh /opt/immich/library
docker compose logs --since=15m immich-server immich-machine-learning
Do not edit, rename, or delete files inside Immich's upload folders by hand. The backup documentation is explicit: those files should be touched only for backup and restore work because the database tracks asset state.
2. Configure Mobile Backup
Install the Immich mobile app, connect it to https://immich.example.com, sign in with a non-admin user, and enable backup for one album first. Check:
- The app can reach the server on cellular and Wi-Fi.
- Background backup is allowed by the phone operating system.
- Uploads resume after closing and reopening the app.
- Videos upload without Nginx timeout or body-size errors.
- The web timeline shows the same items after processing.
After the small album succeeds, expand to the full camera roll. Large first-time imports can run for hours, so monitor disk space and job queues.
3. Verify Search, Albums, and Sharing
Create an album, add a few test assets, share it with another user, and try search after the machine learning jobs finish. If search or recognition feels slow, check CPU, RAM, and worker logs before adding more users.
Troubleshooting
- Uploads stop at the reverse proxy: Increase
client_max_body_size, keepproxy_request_buffering off, and confirm Nginx forwards websocket headers. - Mobile app cannot discover the server: Verify the public URL, certificate chain, DNS, and the
/.well-known/immichroute. - Database container fails: Check that
DB_DATA_LOCATIONis on local storage with Unix permissions and that.envuses only safe characters inDB_PASSWORD. - Server feels slow during first import: Thumbnail, metadata, transcoding, and ML jobs are CPU-heavy. Let queues drain before changing settings.
- Machine learning container exits: Confirm architecture support and available memory. On small hosts, disable ML features or move to a larger VM.
- After restore, assets are missing: The database backup and media folders were likely not from the same point in time. Restore matching database and filesystem copies.
- Certbot cannot issue a certificate: Check DNS, firewall, and whether another Nginx default site is answering the hostname.
- Disk fills unexpectedly: Original uploads, thumbnails, encoded videos, profile images, and database dumps all consume space. Use
du -sh /opt/immich/*to locate growth.
Scaling, Securing, and Next Steps
Backups are the most important day-two task. Immich automatically creates database dumps for disaster recovery, but those dumps do not include photos or videos. A complete backup needs the database and the media folders together.
Create a simple manual backup checkpoint:
cd /opt/immich
mkdir -p backups
BACKUP_DATE="$(date +%F-%H%M)"
docker exec -t immich_postgres \
pg_dump --clean --if-exists --dbname="${DB_DATABASE_NAME:-immich}" --username="${DB_USERNAME:-postgres}" \
| gzip > "backups/immich-db-${BACKUP_DATE}.sql.gz"
rsync -aH --delete /opt/immich/library/ "backups/library-${BACKUP_DATE}/"
ls -lh backups
du -sh backups/library-${BACKUP_DATE}
For a more consistent backup, stop immich-server while copying files, or back up the database first and the filesystem second as the official docs recommend. Then copy the backup set off the server:
rsync -aH --info=progress2 /opt/immich/backups/ backup-user@backup.example.com:/srv/backups/immich/
Caption: A safe Immich recovery plan includes PostgreSQL dumps, original media folders, generated backup files, off-server copies, restore drills, and upgrade notes.
For production hardening, add monitoring for container health, disk usage, backup freshness, TLS renewal, and upload errors. Keep an eye on PostgreSQL storage, prune old local backup staging files after off-server copies are confirmed, and schedule restore drills. If several users upload large videos, move media storage to a larger local disk or NAS-mounted backup target, but keep PostgreSQL itself on local SSD storage.
When you are ready to upgrade, take a fresh backup, read the release notes, then run:
cd /opt/immich
docker compose pull
docker compose up -d
docker compose logs --since=10m immich-server
Conclusion
Following this guide gives you a self-hosted Immich instance with Docker Compose, private local service binding, HTTPS through Nginx, dedicated media and database paths, mobile backup checks, and a practical recovery routine. The outcome is a private photo and video library that can replace basic hosted camera-roll backup while keeping your originals, metadata, and access control under your administration. Next, invite one trusted user, complete a full phone backup, copy a database-plus-files backup off the server, and practice a restore before relying on Immich as the only home for irreplaceable memories.