ansible-playbooks/roles/immich
2025-12-20 23:14:26 +01:00
..
defaults feat: introduce immich 2025-11-14 00:23:03 +01:00
handlers feat: introduce immich 2025-11-14 00:23:03 +01:00
meta feat: introduce immich 2025-11-14 00:23:03 +01:00
tasks feat: new services and fixes 2025-12-20 20:52:24 +01:00
templates fix: podman user called by systemd 2025-12-20 23:14:26 +01:00
README.md feat: new services and fixes 2025-12-20 20:52:24 +01:00

Immich Role

This Ansible role deploys Immich - a high performance self-hosted photo and video management solution - using Podman with docker-compose files.

Requirements

  • Podman installed on the target system (handled by the podman role dependency)
  • Podman compose support (podman compose command available)
  • Sufficient disk space for photos/videos at the upload location

Role Variables

See defaults/main.yml for all available variables and their default values.

Key Configuration Requirements

Required Passwords

Both passwords must be set in your inventory (min 12 characters):

  • immich_postgres_password - PostgreSQL database password
  • immich_valkey_password - Valkey/Redis password

Valkey ACL Configuration

Important: Immich requires a dedicated Valkey ACL user with specific permissions. This role provides the ACL configuration, but you must register it with the Valkey role.

Required Setup in Inventory:

Add the Immich user to your valkey_acl_users list in your inventory or host_vars:

# inventory/host_vars/yourserver.yml or group_vars/all.yml
valkey_acl_users:
  - username: immich
    password: "{{ immich_valkey_password }}"
    keypattern: "immich_bull* immich_channel*"
    commands: "&* -@dangerous +@read +@write +@pubsub +select +auth +ping +info +eval +evalsha"

ACL Breakdown:

  • keypattern: "immich_bull* immich_channel*" - Restricts access to BullMQ keys used by Immich
  • &* - Allow all pub/sub channels (required for BullMQ job queues)
  • -@dangerous - Deny dangerous commands (FLUSHDB, FLUSHALL, KEYS, etc.)
  • +@read +@write - Allow read/write command groups
  • +@pubsub - Allow pub/sub commands (SUBSCRIBE, PUBLISH, etc.)
  • +select - Allow SELECT command (database switching)
  • +auth +ping +info - Connection management commands
  • +eval +evalsha - Lua scripting (required by BullMQ for atomic operations)

Based on: Immich GitHub Discussion #19727

Security Benefits:

  • Immich cannot access keys from other services
  • Cannot execute admin commands (FLUSHDB, CONFIG, etc.)
  • Cannot view all keys (KEYS command denied)
  • Defense-in-depth with ACL + key patterns + database numbers

External Network Configuration

Immich requires a dedicated external network to be defined in your inventory. Add this to your host_vars or group_vars:

podman_external_networks:
  - name: immich
    subnet: 172.20.0.0/16
    gateway: 172.20.0.1

How it works:

  1. Define the Immich network in podman_external_networks list in your inventory
  2. The podman role (a dependency) creates the external network before Immich deployment
  3. The Immich docker-compose file references this external network
  4. The network persists across container restarts and compose stack rebuilds

Dependencies

This role depends on:

  • podman - Container runtime
  • postgres - PostgreSQL database
  • valkey - Redis-compatible cache (formerly Redis)

Note: The Valkey role must be configured with the Immich ACL user (see Valkey Configuration section above) before running this role.

Example Playbook

---
- hosts: servers
  become: true
  roles:
    - role: podman
    - role: immich
      vars:
        immich_postgres_password: "your-secure-postgres-password"
        immich_valkey_password: "your-secure-valkey-password"
        immich_upload_location: /mnt/storage/immich/upload
        immich_timezone: America/New_York

Complete Example with Valkey ACL:

In inventory/host_vars/yourserver.yml:

# Podman external networks
podman_external_networks:
  - name: immich
    subnet: 172.20.0.0/16
    gateway: 172.20.0.1

# Valkey admin password
valkey_admin_password: "your-valkey-admin-password"

# Valkey ACL users - register all service users here
valkey_acl_users:
  - username: immich
    password: "{{ immich_valkey_password }}"
    keypattern: "immich_bull* immich_channel*"
    commands: "&* -@dangerous +@read +@write +@pubsub +select +auth +ping +info +eval +evalsha"
  # Add other services here as needed

# Immich passwords
immich_postgres_password: "your-secure-postgres-password"
immich_valkey_password: "your-secure-valkey-password"

In your playbook:

---
- hosts: servers
  become: true
  roles:
    - role: valkey    # Must run first to create ACL users
    - role: postgres
    - role: podman
    - role: immich

Architecture

The role deploys Immich using Podman containers that connect to shared system services:

Immich Containers:

  1. immich-server - Main application server (exposed on configured port)
  2. immich-machine-learning - ML service for facial recognition and object detection

Shared System Services: 3. PostgreSQL - Database with vector extensions (from postgres role) 4. Valkey - Redis-compatible cache (from valkey role)

Container Networking

Both Immich containers run on a dedicated external Podman network with its own CIDR block. The network is created by the podman role as an external network, referenced in the compose file:

networks:
  immich:
    external: true
    name: immich

The actual network configuration (subnet: 172.20.0.0/16, gateway: 172.20.0.1) is handled by the podman role based on the immich_network_* variables.

This provides:

  • Network isolation: Separate subnet (defined in inventory, e.g., 172.20.0.0/16) from other containers
  • Network persistence: Network survives compose stack rebuilds and container recreation
  • Named bridge: Explicit interface naming for the network
  • Container-to-container communication: The server reaches the ML container via service name (immich-machine-learning:3003) using Docker/Podman internal DNS
  • Container-to-host communication: Both containers can reach PostgreSQL and Valkey on the host via host.containers.internal:{{ podman_subnet_gateway }}

Key Points:

  • The network must be defined in your inventory via podman_external_networks
  • The network is created by the podman role before Immich deployment (via role dependency)
  • The Immich network has its own gateway (e.g., 172.20.0.1 as defined in inventory)
  • extra_hosts maps host.containers.internal to the Podman default bridge gateway (e.g., 10.88.0.1), not the Immich network gateway
  • This allows containers to route to the host machine for PostgreSQL/Valkey access

Checking the network:

# List all Podman networks
podman network ls

# Inspect the Immich network
podman network inspect immich

Data Isolation

The role implements proper data isolation for both database backends:

  • PostgreSQL: Immich gets its own database (immich) and dedicated user (immich) with restricted privileges (NOSUPERUSER, NOCREATEDB, NOCREATEROLE)
  • Valkey: Immich uses a dedicated ACL user (immich) with:
    • Dedicated password (independent from valkey_admin_password)
    • Key pattern restriction (immich_bull* and immich_channel* only)
    • Command restrictions (no admin/dangerous operations like FLUSHDB, CONFIG)
    • Database number isolation (uses DB 0 by default, configurable)
    • Pub/sub channel access for BullMQ job queues

Security Benefits:

  • Each service has unique credentials
  • Compromised service cannot access other services' data
  • Cannot accidentally delete all data (FLUSHDB/FLUSHALL denied)
  • Cannot view keys from other services (KEYS command denied)
  • Defense-in-depth: ACL + key patterns + command restrictions + database numbers

The compose file is deployed to {{ podman_projects_dir }}/immich/docker-compose.yml and managed via a systemd service.

Nginx Reverse Proxy with ACME/Let's Encrypt

The role includes an Nginx vhost template with native ACME support for automatic HTTPS certificate management.

Prerequisites:

  1. Nginx role deployed with acme_email configured
  2. Port 80/443 accessible from internet (for ACME HTTP-01 challenge)
  3. DNS pointing to your server

Configuration:

# Enable Nginx reverse proxy
immich_nginx_enabled: true
immich_nginx_hostname: "blog.hello.com"

# In nginx role configuration (host_vars or group_vars)
acme_email: "admin@blog.com"

What it does:

  • Deploys HTTPS vhost with automatic Let's Encrypt certificate
  • HTTP → HTTPS redirect
  • Proxies to Immich container on localhost
  • Handles WebSocket upgrades for live photos
  • Large file upload support (50GB max)

ACME automatic features:

  • Certificate issuance on first deployment
  • Automatic renewal
  • HTTP-01 challenge handling

Post-Installation

After deployment:

  1. Access Immich at:
    • With Nginx enabled: https://{{ immich_nginx_hostname }}
    • Without Nginx: http://<host-ip>:{{ immich_port }}
  2. Create an admin account on first login
  3. Configure mobile/desktop apps to point to your server

Management

The role creates a systemd service for managing the compose stack:

# Check status
systemctl status immich

# Stop Immich
systemctl stop immich

# Start Immich
systemctl start immich

# Restart Immich
systemctl restart immich

# View logs for all containers
cd /opt/podman/immich && podman compose logs -f

# View logs for specific service
cd /opt/podman/immich && podman compose logs -f immich-server

Manual Management

You can also manage containers directly with podman compose:

cd /opt/podman/immich

# Start services
podman compose up -d

# Stop services
podman compose down

# Pull latest images
podman compose pull

# Recreate containers
podman compose up -d --force-recreate

Updating Immich

To update to a newer version:

  1. Update the immich_version variable in your playbook or inventory
  2. Re-run the Ansible playbook
  3. The systemd service will restart with the new version

Or manually:

cd /opt/podman/immich
podman compose pull
systemctl restart immich

Storage

  • Upload location: Stores all photos, videos, and thumbnails
  • Database location: PostgreSQL data (not suitable for network shares)
  • Model cache: ML models for facial recognition

Ensure adequate disk space and regular backups of these directories.

Files Deployed

  • {{ podman_projects_dir }}/immich/docker-compose.yml - Compose definition
  • /etc/systemd/system/immich.service - Systemd service unit

Security Considerations

  • Set strong passwords for both immich_postgres_password and immich_valkey_password (min 12 chars)
  • Use Ansible Vault to encrypt passwords in production:
    ansible-vault encrypt_string 'your-password' --name 'immich_postgres_password'
    ansible-vault encrypt_string 'your-password' --name 'immich_valkey_password'
    
  • Configure Valkey ACL properly (see Valkey Configuration section) - do not use +@all
  • Consider using a reverse proxy (nginx/traefik) for HTTPS
  • Restrict access via firewall rules if needed
  • Keep Immich updated by changing immich_version and redeploying

Troubleshooting

Check service status

systemctl status immich

View compose file

cat /opt/podman/immich/docker-compose.yml

Check container status

cd /opt/podman/immich
podman compose ps

View logs

cd /opt/podman/immich
podman compose logs

Valkey ACL Issues

Error: "NOPERM No permissions to access a channel"

  • The Valkey ACL is missing channel permissions
  • Ensure &* or +allchannels is in the ACL commands
  • Verify ACL is properly loaded: valkey-cli ACL LIST

Error: "NOAUTH Authentication required"

  • Check immich_valkey_password is set correctly
  • Verify the password matches in both inventory ACL config and immich vars

Error: "WRONGPASS invalid username-password pair"

  • Ensure the Immich user is registered in valkey_acl_users
  • Check the Valkey ACL file was deployed: cat /etc/valkey/users.acl
  • Restart Valkey to reload ACL: systemctl restart valkey

Verify Valkey ACL Configuration:

# Connect as admin
valkey-cli
AUTH default <valkey_admin_password>

# List all ACL users
ACL LIST

# Check specific user
ACL GETUSER immich

# Monitor commands (useful for debugging permissions)
MONITOR

Test Immich user credentials:

valkey-cli
AUTH immich <immich_valkey_password>
SELECT 0
PING
# Should return PONG

# Try a restricted command (should fail)
FLUSHDB
# Should return: (error) NOPERM

License

MIT

Author Information

Created for deploying Immich on NAS systems using Podman and docker-compose.