Setting up an image gallery under Docker
Installing and Securing Immich
Mac Mini · Docker · Nginx Proxy Manager · Cloudflare DNS-only
Prerequisites
- Docker Desktop running
proxy-netnetwork exists:docker network inspect proxy-net- Ports 80 and 443 forwarded on your router
- Cloudflare A record for
photos.plainshawk.co.ukpointing to your public IP, grey cloud
Stage 1 — Create the Directory Structure
bash
mkdir -p ~/docker/immich
cd ~/docker/immichStage 2 — Download the Official Files
Always use the release files rather than the main branch — they're guaranteed compatible:
bash
curl -L https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml \
-o docker-compose.yml
curl -L https://github.com/immich-app/immich/releases/latest/download/example.env \
-o .envStage 3 — Configure the Environment File
bash
nano .envSet these values:
env
# Where your photos are stored
UPLOAD_LOCATION=/Volumes/Media-Home/photos
# Where the database is stored — keep on fast internal SSD
DB_DATA_LOCATION=./postgres
# Your timezone
TZ=Europe/London
# Track latest stable release
IMMICH_VERSION=release
# Strong database password — change this
DB_PASSWORD=choose_a_strong_passwordStage 4 — Modify the Compose File
The official compose file needs two additions for your environment — the proxy-net network and a note on the well-known endpoint:
bash
nano docker-compose.ymlFind the immich-server service and add the networks section:
yaml
immich-server:
# ... existing config unchanged ...
networks:
- default
- proxy-netAdd at the very bottom of the file:
yaml
networks:
default:
proxy-net:
external: trueLeave all other services (immich-machine-learning, database, redis) unchanged — they only need the internal default network.
Stage 5 — Start Immich
bash
docker compose up -dWatch startup — first run takes 3-5 minutes as it initialises the database:
bash
docker compose logs -f immich-serverWait for Immich Server is listening on 0.0.0.0:2283 before proceeding. Press Ctrl+C to stop following.
Check all containers are healthy:
bash
docker compose psYou should see four containers all running:
immich_serverimmich_machine_learningimmich_postgresimmich_redis
Stage 6 — Verify Internal Connectivity
Before touching NPM, confirm the server is responding internally:
bash
docker exec nginx-proxy-manager curl -s http://immich_server:2283/api/server/pingShould return {"res":"pong"}. If it doesn't, the container isn't on proxy-net — recheck Stage 4.
Stage 7 — Add the Proxy Host in NPM
Log into NPM → Proxy Hosts → Add Proxy Host
Details tab:
| Field | Value |
|---|---|
| Domain | photos.plainshawk.co.uk |
| Scheme | http |
| Forward Hostname | immich_server |
| Forward Port | 2283 |
| Cache Assets | Off |
| Block Common Exploits | On |
| Websockets Support | On |
SSL tab:
- Request new Let's Encrypt certificate
- Force SSL: On
- HTTP/2 Support: On
Advanced tab — this is the complete recommended config per Immich's official reverse proxy docs, combined with your scanner blocking rules:
nginx
# Large file uploads — required for RAW files and videos
client_max_body_size 50000M;
# Required by Immich — prevents upload stalling
proxy_request_buffering off;
client_body_buffer_size 1024k;
# Required headers per Immich docs
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;
# Timeouts for large uploads
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
# WebSocket support — required for real-time UI updates
proxy_http_version 1.1;
proxy_redirect off;
location / {
proxy_pass http://immich_server:2283;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Required for mobile app discovery and Let's Encrypt
location /.well-known/immich {
proxy_pass http://immich_server:2283;
}
# Block scanners — Immich uses no PHP, WordPress, or admin panels
location ~ \.(php|php\d|phtml|asp|aspx|cgi)$ {
return 444;
}
location ~ /(wp-admin|wp-login|wp-content|wp-includes|xmlrpc\.php) {
return 444;
}
location ~ /\.(env|git|svn|aws|ssh|npmrc) {
return 444;
}
location ~ /(phpmyadmin|pma|adminer|mysql)/ {
return 444;
}
location ~ /(shell|cmd|exec|system|passthru|eval)/ {
return 444;
}Save.
Stage 8 — First Run Setup
Navigate to https://photos.plainshawk.co.uk:
- Click Getting Started
- Create your admin account — use a strong password
- The first account created is automatically the admin
- Log in
Stage 9 — Secure the Admin Account
Once logged in go to Account Settings:
- Enable Two-Factor Authentication — use an authenticator app (your Vaultwarden already stores TOTP codes)
- Set a strong password if you didn't during setup
- Note your account email — you'll need it for recovery
Stage 10 — Generate API Keys
Go to Account Settings → API Keys:
Create keys for each client that needs access:
| Key Name | Permissions |
|---|---|
immich-kiosk | Assets: Read, Albums: Read, People: Read, Libraries: Read |
mobile-backup | Full access (for the mobile app) |
backup-script | Assets: Read (for your backup script) |
Giving each integration its own minimal-permission key means you can revoke one without affecting the others.
Stage 11 — Configure Immich Settings
Go to Administration → Settings:
Machine Learning:
- Smart Search: On
- Facial Recognition: On
- These run on your Mac Mini's CPU — first scan of a large library takes several hours but subsequent scans are fast
Storage Template:
Go to Administration → Settings → Storage Template and enable it. Set a template like:
{{y}}/{{MM}}/{{dd}}/{{filename}}This organises your files into date-based folders rather than UUID directories.
Trash:
- Enable trash with a 30-day retention — prevents accidental permanent deletion
OAuth (optional but recommended):
- If you want single sign-on across your services, Immich supports OAuth — can integrate with services like Authentik if you add that later
Stage 12 — Mobile App Setup
Install the Immich app on iOS or Android:
- Open the app
- Enter your server URL:
https://photos.plainshawk.co.uk - Log in with your account credentials
- Go to Settings → Background Backup and enable automatic backup
- Select which albums to back up
Stage 13 — Set Up the Backup Script
Update your backup script to include Immich:
bash
nano ~/docker/backups/backup.shConfirm this section is present and correct:
bash
# Immich database
docker exec -t immich_postgres pg_dumpall \
--clean \
--if-exists \
-U postgres \
> $BACKUP_DIR/immich_$DATE.sql
echo "Immich backup done"Stage 14 — Add to Startup Script
bash
nano ~/docker/start-all.shConfirm this is present after NPM starts:
bash
echo "Starting Immich..."
cd /Users/gavin/docker/immich && docker compose up -dStage 15 — Add to Uptime Kuma
Add a monitor at https://status.plainshawk.co.uk:
| Field | Value |
|---|---|
| Name | Immich |
| Monitor Type | HTTP(s) |
| URL | http://immich_server:2283/api/server/ping |
| Heartbeat Interval | 60s |
| Retries | 3 |
Quick Reference
| URL | Purpose |
|---|---|
https://photos.plainshawk.co.uk | Web interface |
https://photos.plainshawk.co.uk/api/server/ping | Health check |
https://photos.plainshawk.co.uk/.well-known/immich | Mobile app discovery |
Troubleshooting
502 Bad Gateway — immich_server isn't on proxy-net. Check:
bash
docker inspect immich_server | grep -A 5 '"Networks"'Mobile app can't connect — The /.well-known/immich location block is missing from NPM's Advanced tab.
Uploads fail over ~1GB — The client_max_body_size or proxy_request_buffering off directive is missing or not being applied. Check NPM's Advanced tab carefully.
Machine learning not working — Check the ML container:
bash
docker compose logs immich-machine-learningPhotos not appearing after upload — Run a library scan: Administration → Jobs → Library → Scan All Libraries.
Database takes a long time to start — Normal on first run. The immich_postgres container initialises the database schema which takes 30-60 seconds. Subsequent starts are much faster.