A Docker Compose setup for Immich — a self-hosted photo and video management application — integrated with Tailscale for secure remote access.
I've only tested this on a mac mini, other systems might run into other error. I made this doc with AI following all my troubleshooting attempts. Hopefully it's useful, but it might have errors. Feel free to contribute updates and fixes if you find any :)
This project automates the deployment of Immich with:
- Immich Server & Machine Learning microservices
- PostgreSQL database with vector support
- Redis for caching
- Tailscale VPN for secure remote access
All services run in isolated Docker containers and communicate through Tailscale's network.
- Docker & Docker Compose installed
- Tailscale account and auth key
- Storage locations for:
- Media uploads (configurable via
UPLOAD_LOCATION) - PostgreSQL database (uses local
./postgresdirectory)
- Media uploads (configurable via
Copy and customize the environment template:
cp .env-template .envEdit .env with your settings:
UPLOAD_LOCATION- Path where photos/videos are storedDB_PASSWORD- PostgreSQL password (change from default)IMMICH_VERSION- Immich version to deploy (e.g.,v1.71.0orv2)TZ- Your timezone
Choose one of two authentication methods:
Option A: Auth Key (expires)
- Generate an auth key from Tailscale Admin Console
- Update
TS_AUTHKEYindocker-compose.yml:- TS_AUTHKEY=tskey-client-YOUR_KEY_HERE - Note: Auth keys expire (default 90 days). You'll need to rotate them periodically.
Option B: OAuth Client (recommended for long-term deployments)
- Create an OAuth client in Tailscale Admin Console
- Give it Write access to Devices/Core and Keys/Auth Keys
- Use the client ID and secret as your auth key:
- TS_AUTHKEY=tskey-oauth-YOUR_CLIENT_ID-YOUR_CLIENT_SECRET - Benefits: No expiration, long-term stability, better for automated deployments
Edit ts-config/immich-funnel.json to expose Immich over Tailscale Funnel:
- Replace
${TS_CERT_DOMAIN}with your domain - Configure proxy rules as needed
docker compose up -dServices will pull latest images and start automatically.
After services start, several critical configuration steps are required for a fully operational zero open ports deployment.
If using an OAuth client, define the tag in your Tailscale Access Control List (ACL):
- Open Tailscale Admin Console > Access Control
- Add
tagOwnersfor your tag:"tagOwners": { "tag:container": ["your-admin-email@example.com"] }
- Go to Settings > OAuth Clients > Generate OAuth Client
- Configure:
- Scope:
Devices: Write - Tag:
tag:container(or your chosen tag)
- Scope:
- Copy the Client Secret and use it as
TS_AUTHKEYin your docker-compose.yml
By default, Tailscale nodes expire after 180 days. For a persistent server, disable expiry:
- Go to Machines tab in Tailscale Admin Console
- Locate your
immichmachine - Click the three dots
...> Disable Key Expiry- Note: OAuth clients often have this disabled automatically, but verify to be sure
Tailscale Funnel acts as a reverse proxy, so Immich needs to trust headers for correct HTTPS handling:
- Log into Immich as an Admin
- Go to Administration > Settings > Server Settings
- Find Proxy Settings
- In Trusted Proxies, add the Docker internal IP range:
172.18.0.0/16- Or add the specific Tailscale container IP
- Set External Domain to
https://immich.your-tailnet.ts.net
If the page loads but gets stuck, check Funnel status:
docker exec -it tailscale-immich tailscale funnel statusImportant: Immich must be served at the root / of the hostname, not a sub-path. Tailscale Funnel automatically translates port 443 to internal port 2283.
To use the Immich mobile app:
- Server URL: Enter
https://immich.your-tailnet.ts.net- Do NOT include a port number; Funnel handles the translation automatically
- Tailscale on Phone: Not required if Funnel is enabled, as the app connects via public internet
- If Funnel is disabled, enable Tailscale on your phone and connect to your Tailnet first
immich-setup/
├── docker-compose.yml # Main Docker configuration (ignored in git)
├── docker-compose-template.yml # Template reference
├── .env # Environment variables (ignored in git)
├── .env-template # Environment template
├── update.sh # Automated update script
├── ts-config/
│ └── immich-funnel.json # Tailscale Funnel config
└── postgres/ # PostgreSQL data (ignored in git)
Run the automated update script:
./update.shThis pulls the latest images and restarts services.
# Tailscale service
docker logs -f tailscale-immich
# Immich server
docker logs -f immich_server
# All services
docker compose logs -f- Local access: Connect via your Tailscale IP on port 443
- Domain access: Use your configured domain if Funnel is enabled
- Default port in compose:
2283(proxied through Tailscale)
| Variable | Description | Default |
|---|---|---|
UPLOAD_LOCATION |
Media storage path | /Volumes/Storage/Pictures/immich |
DB_DATA_LOCATION |
Database storage path | ./postgres |
DB_PASSWORD |
PostgreSQL password | postgres |
IMMICH_VERSION |
Release version to use | v2 |
TZ |
Server timezone | America/Detroit |
See Immich docs for all available variables.
- Never commit
.env,docker-compose.yml, or thepostgres/folder to version control - Change
DB_PASSWORDfrom the default - Rotate Tailscale auth keys regularly
- Store Tailscale auth keys securely outside version control
- Check Docker is running:
docker ps - Review logs:
docker compose logs - Verify
.envexists andUPLOAD_LOCATIONpath is accessible
Run this command to filter errors:
docker logs tailscale-immich 2>&1 | grep -iE "error|warn|fatal|forbidden"Common errors:
403 Forbidden: OAuth client missingDevices: Writescope or tag not assigned correctlyGetting OS base config is not supported: AddTS_USERSPACE=truetodocker-compose.ymlenvironment variables
- Confirm Tailscale authentication:
docker logs tailscale-immich - Check Funnel status:
docker exec -it tailscale-immich tailscale funnel status - Verify firewall rules in Tailscale admin console
- Ensure Trusted Proxies are configured in Immich settings
- Check that External Domain is set to your Tailscale domain
- Verify Funnel is enabled and routing correctly
- Check browser console for failed requests (F12 > Console)
- Ensure no sub-path is being used; Immich must be at
/ - Verify Trusted Proxies include the Docker network range
172.18.0.0/16
- Ensure
DB_DATA_LOCATIONpath has appropriate permissions - Check disk space for PostgreSQL data
- Review database logs:
docker logs immich_postgres
This configuration follows Immich's licensing. See Immich LICENSE.