👍 What we like
- ✓Open-source self-hosted alternative to Google Photos
- ✓Automatic mobile backup for iOS and Android
- ✓Facial recognition and smart search capabilities
- ✓Free with no subscription fees
- ✓Handles HTTPS via Caddy reverse proxy
👎 What to watch
- ✕Resource-intensive machine learning module
- ✕Requires complex multi-container Docker setup
- ✕Needs significant disk space for photo libraries
- ✕Requires manual DNS and firewall configuration
📑 Contents ▾
- 01 Prerequisites
- 02 Step 1: Install Docker and Docker Compose
- 03 Step 2: Configure DNS
- 04 Step 3: Create the Shared Docker Network
- 05 Step 4: Prepare the Folder and .env File
- 06 Step 5: The Immich docker-compose.yml
- 07 Step 6: The Caddyfile for Automatic HTTPS
- 08 Step 7: Start the Services
- 09 Step 8: Create the Admin Account and Configure Apps
- 10 Step 9: Reliable Backups
- 11 Step 10: Final Verification
- 12 Security and Hardening
- 13 Common Pitfalls and Troubleshooting
- 14 FAQ
- · Can Immich really replace Google Photos?
- · How much RAM do I need?
- · What is the difference between an “uploaded” library and an “external” library?
- · Is HTTPS really mandatory?
- · Can I host Immich on a NAS?
- · Immich or PhotoPrism: which one to choose?
- 21 Related Topics
You want to leave Google Photos without giving up automatic phone backup, facial recognition, and a smooth gallery experience? Immich is currently the most mature self-hosted alternative: an open-source application that automatically backs up your photos and videos from your iOS/Android mobile apps, organizes them by date and location, detects faces, and offers smart search — all on your own server, with no subscription.
The catch: Immich is a multi-container service (server, machine learning, PostgreSQL, Redis) hosting irreplaceable data. It must therefore be deployed cleanly, with HTTPS and reliable backups. In this tutorial, we deploy Immich via Docker Compose, behind a Caddy reverse proxy that handles HTTPS and automatic Let’s Encrypt certificates.
Prerequisites
- A server or VPS running Ubuntu 24.04 (or Debian 12) with at least 4 GB of RAM: Immich’s machine learning module is resource-intensive. For large volumes, aim for 8 GB. An entry-level VPS from Hetzner or Scaleway works perfectly, but many users host Immich on a NAS or mini-PC at home for massive storage.
- Lots of disk space. Your photos quickly add up to several hundred gigabytes. Plan accordingly.
- Docker and Docker Compose installed (command in Step 1).
- A domain name for which you control the DNS. We will use
photos.example.com. - Ports 80 and 443 open on your firewall for Let’s Encrypt validation and HTTPS traffic.
Step 1: Install Docker and Docker Compose
If Docker is not present, install it from the official repository (Ubuntu repository versions are often outdated):
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Add your user to the docker group (log out and back in afterwards):
sudo usermod -aG docker $USER
Verify:
docker --version && docker compose version
Step 2: Configure DNS
Caddy needs your domain to point to the server’s IP address to issue the certificate. At your DNS provider, create an A record (and AAAA if IPv6):
| Type | Name | Value |
|---|---|---|
| A | photos.example.com | 203.0.113.10 |
Check propagation from your machine:
dig +short photos.example.com
The command should return your server’s IP. Until it does, Let’s Encrypt will fail to issue the certificate.
Step 3: Create the Shared Docker Network
For Caddy to reach the Immich container by its name, both must share a dedicated Docker network. Create it once:
docker network create web
If you are already following our Caddy reverse proxy tutorial, this network might already exist: the command will return an error, which is harmless; ignore it.
Step 4: Prepare the Folder and .env File
Create your working directory and the environment file. Immich uses a .env file to centralize versions, passwords, and paths:
mkdir -p ~/immich && cd ~/immich
nano .env
# Immich version (pin a stable version rather than latest in production)
IMMICH_VERSION=release
# Library location (uploaded photos/videos). Use a path with plenty of space.
UPLOAD_LOCATION=./library
# PostgreSQL data location (DO NOT put this in a network-mounted folder)
DB_DATA_LOCATION=./postgres
# Database credentials — use a long, unique password
DB_PASSWORD=replace_with_a_long_random_password
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
# Timezone (fixes displayed EXIF dates)
TZ=Europe/Paris
Generate a strong password for the database:
openssl rand -base64 32
Paste the result into DB_PASSWORD.
Step 5: The Immich docker-compose.yml
This is the core of the deployment. Immich orchestrates four containers: the server, the machine learning module, PostgreSQL (with the vector extension for similarity search), and Redis.
nano docker-compose.yml
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
depends_on:
- redis
- database
restart: unless-stopped
healthcheck:
disable: false
networks:
- web
- immich-internal
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
env_file:
- .env
restart: unless-stopped
healthcheck:
disable: false
networks:
- immich-internal
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm
healthcheck:
test: redis-cli ping || exit 1
restart: unless-stopped
networks:
- immich-internal
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: unless-stopped
networks:
- immich-internal
volumes:
model-cache:
networks:
web:
external: true
immich-internal:
The Immich server is on two networks: immich-internal to talk to its database and Redis, and web to be reachable by Caddy. PostgreSQL, Redis, and the ML module are only on the internal network, never accessible from the outside. Note the absence of a ports: section: Immich is not exposed directly; all traffic goes through the reverse proxy.
Step 6: The Caddyfile for Automatic HTTPS
If you already have Caddy in place, simply add the site block. Otherwise, create a Caddy directory:
mkdir -p ~/caddy && cd ~/caddy
nano Caddyfile
{
email admin@example.com
}
photos.example.com {
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# Immich sends large files: increase body size limits
request_body {
max_size 50000MB
}
reverse_proxy immich_server:2283
}
The request_body max_size directive is crucial: without it, uploading large video files would fail. Immich’s internal port is 2283.
Then, the Caddy docker-compose.yml:
nano docker-compose.yml
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:
external: true
The caddy_data volume stores your certificates: without it, you would request a new certificate on every restart and quickly hit Let’s Encrypt rate limits.
Step 7: Start the Services
Start Immich first (the first launch downloads several images, so be patient), then Caddy:
cd ~/immich && docker compose up -d
cd ~/caddy && docker compose up -d
Follow Immich’s logs to see the server start up:
cd ~/immich && docker compose logs -f immich-server
When you see Immich Server is listening on ..., visit https://photos.example.com. The administrator account creation page will appear.
Step 8: Create the Admin Account and Configure Apps
The first account created automatically becomes the administrator. Choose a strong email and password. Then, in the admin panel:
- Disable public registration if you don’t need it, and manually create accounts for your family (Administration → Users).
- Install the Immich app on iOS or Android, enter the URL
https://photos.example.com, and log in. - In the mobile app, enable automatic backup: select the albums to upload. Your photos now upload automatically, just like with Google Photos.
Facial recognition and smart search run in the background via the machine learning container. On a large initial import, let it work for several hours; you can track progress in Administration → Tasks.
Step 9: Reliable Backups
Immich hosts irreplaceable memories. Two things must be backed up: the PostgreSQL database (metadata, albums, faces) and the file library (UPLOAD_LOCATION). A simple cp of the live database would corrupt it: use a consistent dump.
nano ~/immich/backup.sh
#!/bin/bash
set -euo pipefail
cd "$HOME/immich"
BACKUP_DIR="$HOME/immich/backups"
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# Consistent database dump (the official method recommended by Immich)
docker compose exec -T database \
pg_dumpall --clean --if-exists --username=postgres \
| gzip > "$BACKUP_DIR/immich-db-$STAMP.sql.gz"
# Keep the last 14 database dumps
find "$BACKUP_DIR" -name 'immich-db-*.sql.gz' -mtime +14 -delete
chmod +x ~/immich/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * $HOME/immich/backup.sh") | crontab -
The file library (often hundreds of gigabytes) should be backed up separately, ideally incrementally and off-site. Our guide automatic backup with Restic and Backblaze shows how to replicate this folder to an encrypted remote storage. A backup on the same server protects against neither fire nor ransomware.
Step 10: Final Verification
Check that everything is in place:
# Containers are running
docker compose -f ~/immich/docker-compose.yml ps
# HTTP redirects to HTTPS (code 308)
curl -sI http://photos.example.com | head -1
# HTTPS responds and the certificate is valid
curl -sI https://photos.example.com | head -1
Check the internal health status from the Administration → Status panel: all services (database, ML, Redis) should be green.
Security and Hardening
- Strict UFW firewall. Open only 80, 443, and SSH. Internal ports (PostgreSQL 5432, Redis, ML) are never exposed — the network configuration handles this already.
- Strong passwords for the admin account and database, public registration disabled, and 2FA enabled from user settings.
- Controlled updates. Immich evolves quickly, and some versions require database migrations. Back up before every update, read the release notes, then run
docker compose pull && docker compose up -d. Pin a specific version in.envrather thanreleasein production.
To learn more about securing an exposed server, check out install and secure an Ubuntu VPS.
Common Pitfalls and Troubleshooting
- Blocked uploads / 413 error. Reverse proxy size limit: check
request_body max_sizein the Caddyfile (Step 6). With Nginx Proxy Manager, increaseclient_max_body_size. - ML container fails to start or runs out of RAM. The machine learning container can be killed (OOM) on a 2 GB server. Add RAM or swap.
- Database fails to start after an update. Almost always a failed migration on a skipped version. Restore the last dump and proceed version by version.
DB_DATA_LOCATIONon a network share. PostgreSQL requires a local POSIX filesystem: an NFS/SMB mount will corrupt the database. Keep it on a local disk.
FAQ
Can Immich really replace Google Photos?
For daily use, yes: automatic phone backup, chronological gallery, shared albums, location map, facial recognition, and keyword search (“beach”, “dog”). The mobile experience is very similar. The major difference: you are responsible for storage and backups, but you retain full control over your data.
How much RAM do I need?
Count on a minimum of 4 GB, mainly due to the machine learning module that indexes faces and objects. For very large libraries or multiple active users, 8 GB is preferable.
What is the difference between an “uploaded” library and an “external” library?
The uploaded library corresponds to files sent from your apps (Immich manages and organizes them). An external library points to an existing folder mounted read-only: Immich indexes it without moving it. Useful for scanning a collection already organized on a NAS.
Is HTTPS really mandatory?
Yes. Mobile apps send your credentials and photos over the network; with HTTP, everything travels in cleartext, and some mobile features refuse insecure connections. The Caddy reverse proxy in this tutorial handles this automatically.
Can I host Immich on a NAS?
Yes, this is a very common use case to take advantage of large storage. Synology (Container Manager), QNAP, and especially TrueNAS support Docker, with the same logic: containers behind an HTTPS reverse proxy. See our comparison Synology vs TrueNAS vs QNAP.
Immich or PhotoPrism: which one to choose?
Immich excels in automatic mobile backup and the Google Photos-like experience; PhotoPrism is renowned for its organization and indexing. Our comparison Immich vs PhotoPrism details the strengths of each.
Related Topics
- Immich vs PhotoPrism: which self-hosted photo gallery
- Immich vs PhotoPrism vs Nextcloud Photos
- Automatic HTTPS reverse proxy with Caddy and Docker
- Automatic backup with Restic and Backblaze
You now have your own Google Photos, in HTTPS, with automatic mobile backup, facial recognition, and 100% sovereign data. It is one of the services that changes the homelab experience the most. To follow Immich new versions, risky database migrations, and best practices for self-hosting, subscribe to our Telegram watch bot.