Setting up an image gallery under Docker

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-net network exists: docker network inspect proxy-net
  • Ports 80 and 443 forwarded on your router
  • Cloudflare A record for photos.plainshawk.co.uk pointing to your public IP, grey cloud

Stage 1 — Create the Directory Structure

bash

mkdir -p ~/docker/immich
cd ~/docker/immich

Stage 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 .env

Stage 3 — Configure the Environment File

bash

nano .env

Set 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_password

Stage 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.yml

Find the immich-server service and add the networks section:

yaml

  immich-server:
    # ... existing config unchanged ...
    networks:
      - default
      - proxy-net

Add at the very bottom of the file:

yaml

networks:
  default:
  proxy-net:
    external: true

Leave all other services (immich-machine-learningdatabaseredis) unchanged — they only need the internal default network.


Stage 5 — Start Immich

bash

docker compose up -d

Watch startup — first run takes 3-5 minutes as it initialises the database:

bash

docker compose logs -f immich-server

Wait 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 ps

You should see four containers all running:

  • immich_server
  • immich_machine_learning
  • immich_postgres
  • immich_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/ping

Should 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:

FieldValue
Domainphotos.plainshawk.co.uk
Schemehttp
Forward Hostnameimmich_server
Forward Port2283
Cache AssetsOff
Block Common ExploitsOn
Websockets SupportOn

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:

  1. Click Getting Started
  2. Create your admin account — use a strong password
  3. The first account created is automatically the admin
  4. 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 NamePermissions
immich-kioskAssets: Read, Albums: Read, People: Read, Libraries: Read
mobile-backupFull access (for the mobile app)
backup-scriptAssets: 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:

  1. Open the app
  2. Enter your server URL: https://photos.plainshawk.co.uk
  3. Log in with your account credentials
  4. Go to Settings → Background Backup and enable automatic backup
  5. 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.sh

Confirm 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.sh

Confirm this is present after NPM starts:

bash

echo "Starting Immich..."
cd /Users/gavin/docker/immich && docker compose up -d

Stage 15 — Add to Uptime Kuma

Add a monitor at https://status.plainshawk.co.uk:

FieldValue
NameImmich
Monitor TypeHTTP(s)
URLhttp://immich_server:2283/api/server/ping
Heartbeat Interval60s
Retries3

Quick Reference

URLPurpose
https://photos.plainshawk.co.ukWeb interface
https://photos.plainshawk.co.uk/api/server/pingHealth check
https://photos.plainshawk.co.uk/.well-known/immichMobile 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-learning

Photos 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.