From f1999a7813a7f8fe3af4de4316286cf85b6477c4 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 25 Nov 2025 12:33:30 -0800 Subject: [PATCH] Initial commit: Immich Docker setup - Add docker-compose.yml with Immich v2 configuration - Include all required services (server, ML, postgres, redis) - Add .env.example with secure defaults - Create comprehensive README with setup instructions - Add backup and health check scripts - Configure .gitignore for data directories Deployed and tested on Netcup VPS at photos.jeffemmett.com Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 22 +++ .gitignore | 20 +++ README.md | 278 +++++++++++++++++++++++++++++++++++++ docker-compose.yml | 72 ++++++++++ scripts/backup-database.sh | 35 +++++ scripts/health-check.sh | 85 ++++++++++++ 6 files changed, 512 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100755 scripts/backup-database.sh create mode 100755 scripts/health-check.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..47edbf7 --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables + +# The location where your uploaded files are stored +UPLOAD_LOCATION=./library + +# The location where your database files are stored. Network shares are not supported for the database +DB_DATA_LOCATION=./postgres + +# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List +TZ=Etc/UTC + +# The Immich version to use. You can pin this to a specific version like "v2.1.0" +IMMICH_VERSION=v2 + +# Connection secret for postgres. You should change it to a random password +# Please use only the characters `A-Za-z0-9`, without special characters or spaces +DB_PASSWORD=CHANGE_ME_TO_RANDOM_PASSWORD + +# The values below this line do not need to be changed +################################################################################### +DB_USERNAME=postgres +DB_DATABASE_NAME=immich diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71f3ca7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Environment files +.env + +# Data directories +library/ +postgres/ +backups/ +upload/ +uploads/ +thumbs/ +profile/ +video/ +encoded-video/ + +# Logs +*.log + +# OS files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf23346 --- /dev/null +++ b/README.md @@ -0,0 +1,278 @@ +# Immich Docker Setup + +Self-hosted photo and video management solution with Docker Compose. + +## Overview + +This repository contains a production-ready Docker Compose setup for [Immich](https://immich.app/), a high-performance self-hosted photo and video management solution. + +## Features + +- 🐳 Easy Docker Compose deployment +- 🔒 Secure configuration with environment variables +- 📦 Automated daily database backups +- 🔄 Health checks for all services +- 🚀 Reverse proxy ready (Caddy/Nginx) +- 📱 Mobile app support (iOS/Android) + +## Architecture + +This setup includes the following services: + +- **immich-server**: Main application server +- **immich-machine-learning**: AI-powered photo recognition and classification +- **postgres**: Database with vector extensions for AI features +- **redis/valkey**: Caching layer + +## Quick Start + +### Prerequisites + +- Docker 20.10+ and Docker Compose v2.0+ +- At least 4GB RAM +- 10GB+ storage (plus space for your media) + +### Installation + +1. Clone this repository: + ```bash + git clone https://gitea.jeffemmett.com/jeff/immich-docker.git + cd immich-docker + ``` + +2. Create your environment file: + ```bash + cp .env.example .env + ``` + +3. Generate a secure database password: + ```bash + # On Linux/macOS + openssl rand -base64 32 | tr -d "=+/" | cut -c1-32 + + # Or use this one-liner to update .env directly + sed -i "s/CHANGE_ME_TO_RANDOM_PASSWORD/$(openssl rand -base64 32 | tr -d '=+\/' | cut -c1-32)/" .env + ``` + +4. (Optional) Customize settings in `.env`: + - `UPLOAD_LOCATION`: Where your photos/videos are stored + - `DB_DATA_LOCATION`: Database storage location + - `TZ`: Your timezone + - `IMMICH_VERSION`: Pin to specific version if desired + +5. Start the services: + ```bash + docker compose up -d + ``` + +6. Access Immich at `http://localhost:2283` + +## Configuration + +### Environment Variables + +All configuration is done through the `.env` file. See `.env.example` for all available options. + +Key variables: +- `UPLOAD_LOCATION`: Media storage path (default: `./library`) +- `DB_DATA_LOCATION`: Database path (default: `./postgres`) +- `DB_PASSWORD`: PostgreSQL password (**must change before first run**) +- `IMMICH_VERSION`: Version tag (default: `v2`) +- `TZ`: Timezone (default: `Etc/UTC`) + +### Reverse Proxy Setup + +#### With Caddy + +Create a Caddyfile: +```caddy +photos.yourdomain.com { + reverse_proxy localhost:2283 + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "no-referrer-when-downgrade" + } +} +``` + +#### With Nginx + +```nginx +server { + listen 443 ssl http2; + server_name photos.yourdomain.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://localhost:2283; + 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; + } +} +``` + +## Backup & Restore + +### Automated Backups + +Daily backups are configured via cron (if using the provided scripts): + +```bash +# Run backup manually +./scripts/backup-database.sh + +# Set up automated daily backups (2 AM) +crontab -e +# Add: 0 2 * * * /path/to/immich-docker/scripts/backup-database.sh >> /var/log/immich-backup.log 2>&1 +``` + +### Manual Backup + +```bash +# Backup database +docker compose exec -T database pg_dump -U postgres immich | gzip > backup-$(date +%Y%m%d_%H%M%S).sql.gz + +# Backup uploaded files +tar -czf library-backup-$(date +%Y%m%d).tar.gz library/ +``` + +### Restore from Backup + +```bash +# Restore database +gunzip -c backup.sql.gz | docker compose exec -T database psql -U postgres immich + +# Restore files +tar -xzf library-backup.tar.gz +``` + +## Maintenance + +### Update Immich + +```bash +# Pull latest images +docker compose pull + +# Restart with new images +docker compose up -d + +# Remove old images +docker image prune -f +``` + +### View Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f immich-server + +# Last 100 lines +docker compose logs --tail=100 +``` + +### Health Check + +```bash +# Check container status +docker compose ps + +# Run health check script +./scripts/health-check.sh +``` + +## Mobile Apps + +Download the Immich mobile app to automatically backup your photos: + +- **iOS**: [App Store - Immich](https://apps.apple.com/app/immich/id1613945652) +- **Android**: [Play Store - Immich](https://play.google.com/store/apps/details?id=app.alextran.immich) + +Configure the app with: +- Server URL: `https://photos.yourdomain.com` (or your server address) +- Email/Password: Created during first-time setup + +## Troubleshooting + +### Cannot connect to Immich + +1. Check if containers are running: + ```bash + docker compose ps + ``` + +2. Check logs for errors: + ```bash + docker compose logs immich-server + ``` + +3. Verify port is accessible: + ```bash + curl http://localhost:2283 + ``` + +### Database connection errors + +1. Check database is healthy: + ```bash + docker compose exec database pg_isready -U postgres + ``` + +2. Verify DB_PASSWORD matches in `.env` + +### Out of disk space + +1. Check Docker disk usage: + ```bash + docker system df + ``` + +2. Clean up old images and containers: + ```bash + docker system prune -a + ``` + +## Hardware Acceleration + +For better performance with video transcoding and ML inference, enable hardware acceleration: + +1. Uncomment the appropriate `extends` section in `docker-compose.yml` +2. Download the appropriate hwaccel file from Immich docs +3. Restart services + +See [Immich Hardware Acceleration Docs](https://docs.immich.app/features/ml-hardware-acceleration) for details. + +## Security Considerations + +- ✅ Change `DB_PASSWORD` to a strong random password +- ✅ Use HTTPS with a reverse proxy +- ✅ Keep Immich updated regularly +- ✅ Restrict port 2283 to localhost if using a reverse proxy +- ✅ Enable firewall on your server +- ✅ Regular backups of database and media files + +## Documentation + +- [Official Immich Docs](https://docs.immich.app/) +- [Docker Installation Guide](https://docs.immich.app/install/docker-compose) +- [Environment Variables Reference](https://docs.immich.app/install/environment-variables) + +## License + +This setup configuration is MIT licensed. Immich itself is AGPL-3.0 licensed. + +## Support + +- [Immich GitHub](https://github.com/immich-app/immich) +- [Immich Discord](https://discord.gg/immich) +- [Immich Documentation](https://docs.immich.app/) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..87d6344 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,72 @@ +# +# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose +# +# Make sure to use the docker-compose.yml of the current release: +# +# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml +# +# The compose file on main may not be compatible with the latest release. + +services: + immich-server: + container_name: immich_server + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + # extends: + # file: hwaccel.transcoding.yml + # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding + volumes: + # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file + - ${UPLOAD_LOCATION}:/data + - /etc/localtime:/etc/localtime:ro + env_file: + - .env + ports: + - '2283:2283' + depends_on: + - redis + - database + restart: always + healthcheck: + disable: false + + immich-machine-learning: + container_name: immich_machine_learning + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. + # Example tag: ${IMMICH_VERSION:-release}-cuda + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + # extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration + # file: hwaccel.ml.yml + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable + volumes: + - model-cache:/cache + env_file: + - .env + restart: always + healthcheck: + disable: false + + redis: + container_name: immich_redis + image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa + healthcheck: + test: redis-cli ping || exit 1 + restart: always + + database: + container_name: immich_postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_INITDB_ARGS: '--data-checksums' + # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs + # DB_STORAGE_TYPE: 'HDD' + volumes: + # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file + - ${DB_DATA_LOCATION}:/var/lib/postgresql/data + shm_size: 128mb + restart: always + +volumes: + model-cache: diff --git a/scripts/backup-database.sh b/scripts/backup-database.sh new file mode 100755 index 0000000..b9b1e7c --- /dev/null +++ b/scripts/backup-database.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Immich Database Backup Script + +BACKUP_DIR=~/immich/backups +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="$BACKUP_DIR/immich-db-$TIMESTAMP.sql.gz" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +echo "Starting database backup: $(date)" + +# Create backup +cd ~/immich +docker-compose exec -T postgres pg_dumpall -U postgres | gzip > "$BACKUP_FILE" + +if [ $? -eq 0 ]; then + echo "✓ Backup successful: $BACKUP_FILE" + + # Get backup size + SIZE=$(du -h "$BACKUP_FILE" | cut -f1) + echo " Size: $SIZE" + + # Keep only last 30 days of backups + find "$BACKUP_DIR" -name "immich-db-*.sql.gz" -mtime +30 -delete + + # Count remaining backups + COUNT=$(ls -1 "$BACKUP_DIR"/immich-db-*.sql.gz 2>/dev/null | wc -l) + echo " Total backups: $COUNT" +else + echo "✗ Backup failed!" + exit 1 +fi + +echo "Backup completed: $(date)" diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100755 index 0000000..df6c99d --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Immich Health Check Script + +echo "=========================================" +echo "Immich Health Check - $(date)" +echo "=========================================" +echo "" + +# Check Docker containers +echo "📦 Docker Containers:" +cd ~/immich +CONTAINERS=$(docker-compose ps --services) +ALL_UP=true + +for service in $CONTAINERS; do + STATUS=$(docker-compose ps -q $service | xargs docker inspect -f '{{.State.Status}}') + if [ "$STATUS" = "running" ]; then + echo " ✓ $service: running" + else + echo " ✗ $service: $STATUS" + ALL_UP=false + fi +done +echo "" + +# Check Immich API +echo "🌐 Immich API:" +API_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:2283/api/server/ping 2>/dev/null) +if [ "$API_RESPONSE" = "200" ]; then + echo " ✓ API responding (HTTP $API_RESPONSE)" +else + echo " ✗ API not responding (HTTP $API_RESPONSE)" + ALL_UP=false +fi +echo "" + +# Check disk space +echo "💾 Storage Usage:" +USAGE=$(df -h ~/immich | tail -1 | awk '{print $5}' | sed 's/%//') +AVAILABLE=$(df -h ~/immich | tail -1 | awk '{print $4}') +echo " Disk usage: ${USAGE}%" +echo " Available: $AVAILABLE" + +if [ "$USAGE" -gt 85 ]; then + echo " ⚠️ Warning: Disk usage above 85%" +fi +echo "" + +# Check library size +echo "📸 Library Statistics:" +UPLOAD_SIZE=$(du -sh ~/immich/library 2>/dev/null | cut -f1) +echo " Library size: $UPLOAD_SIZE" + +PHOTO_COUNT=$(find ~/immich/library -type f 2>/dev/null | wc -l) +echo " File count: $PHOTO_COUNT" +echo "" + +# Check latest backup +echo "💾 Latest Backup:" +LATEST_BACKUP=$(ls -t ~/immich/backups/immich-db-*.sql.gz 2>/dev/null | head -1) +if [ -n "$LATEST_BACKUP" ]; then + BACKUP_DATE=$(stat -c %y "$LATEST_BACKUP" | cut -d' ' -f1) + BACKUP_SIZE=$(du -h "$LATEST_BACKUP" | cut -f1) + echo " ✓ Latest backup: $(basename $LATEST_BACKUP)" + echo " Date: $BACKUP_DATE" + echo " Size: $BACKUP_SIZE" + + # Check backup age + BACKUP_AGE=$((($(date +%s) - $(stat -c %Y "$LATEST_BACKUP")) / 86400)) + if [ "$BACKUP_AGE" -gt 1 ]; then + echo " ⚠️ Warning: Backup is $BACKUP_AGE days old" + fi +else + echo " ✗ No backups found" +fi +echo "" + +# Overall status +echo "=========================================" +if [ "$ALL_UP" = true ]; then + echo "Status: ✓ All systems operational" +else + echo "Status: ✗ Issues detected - check above" +fi +echo "========================================="