feat: Add Immich app flake with docker-compose deployment

- Create Immich module as appflake in appflakes/immich/
- Module provides NixOS module for docker-compose deployment
- Includes 5-container stack:
  * immich_server: Web API and interface
  * immich_microservices: Background jobs and ML processing
  * immich_postgres: PostgreSQL with pgvector
  * immich_redis: Redis cache
  * immich_typesense: Search engine
- Configuration options for:
  * Domain and port customization
  * Data directory location
  * Database and API keys
  * Optional Watchtower auto-updates
- Automatic systemd service integration
- Comprehensive documentation in README.md
- Named module 'immich-docker' to avoid conflicts with Nixpkgs module
This commit is contained in:
Julian Sutter 2026-02-03 23:35:34 -08:00
parent 7263c12bfc
commit 532e01cbf1
3 changed files with 626 additions and 0 deletions

209
appflakes/immich/flake.nix Normal file
View file

@ -0,0 +1,209 @@
{
description = "Immich - Self-hosted photo and video backup solution (Docker Compose deployment)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
let
# NixOS module to integrate Immich with the system
nixosModule = { config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.immich-docker;
in
{
options.services.immich-docker = {
enable = mkEnableOption "Immich photo management server (Docker Compose deployment)";
domain = mkOption {
type = types.str;
default = "immich.local";
description = "Domain name for Immich web interface";
};
port = mkOption {
type = types.port;
default = 2283;
description = "Port for Immich web interface";
};
dataDir = mkOption {
type = types.path;
default = "/mnt/userdata/immich";
description = "Directory for Immich data";
};
dbPassword = mkOption {
type = types.str;
default = "immich";
description = "PostgreSQL database password";
};
typesenseApiKey = mkOption {
type = types.str;
default = "immich-typesense-secret-key-change-this";
description = "Typesense search API key";
};
enableWatchtower = mkOption {
type = types.bool;
default = false;
description = "Enable automatic container updates via Watchtower";
};
};
config = mkIf cfg.enable {
# Check for conflicts with the built-in Immich module
assertions = [
{
assertion = !(config.services.immich.enable or false);
message = "services.immich-docker conflicts with services.immich. Please disable the default Nixpkgs Immich module by setting services.immich.enable = false.";
}
];
# Ensure Docker is available
virtualisation.docker.enable = mkDefault true;
# Install docker-compose
environment.systemPackages = with pkgs; [
docker-compose
];
# Create required directories
systemd.tmpfiles.rules = [
"d /etc/compose/immich 0755 root root -"
"d ${cfg.dataDir}/upload 0755 root root -"
"d ${cfg.dataDir}/library 0755 root root -"
"d ${cfg.dataDir}/postgres 0755 root root -"
"d ${cfg.dataDir}/typesense 0755 root root -"
];
# Generate docker-compose configuration
environment.etc."compose/immich/docker-compose.yml".text = ''
version: "3.8"
services:
immich_server:
image: ghcr.io/immich-app/immich-server:v1.122.2
container_name: immich_server
ports:
- "${toString cfg.port}:3000"
environment:
- DB_HOSTNAME=immich_postgres
- DB_USERNAME=immich
- DB_PASSWORD=${cfg.dbPassword}
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=immich_redis
- TYPESENSE_HOST=immich_typesense
- IMMICH_WEB_URL=http://localhost:${toString cfg.port}
- DISABLE_REVERSE_GEOCODING=false
volumes:
- ${cfg.dataDir}/upload:/usr/src/app/upload
- ${cfg.dataDir}/library:/usr/src/app/library
- /etc/localtime:/etc/localtime:ro
depends_on:
- immich_postgres
- immich_redis
- immich_typesense
restart: unless-stopped
${if cfg.enableWatchtower then ''labels:
com.centurylinklabs.watchtower.enable: "true"'' else ""}
immich_microservices:
image: ghcr.io/immich-app/immich-server:v1.122.2
container_name: immich_microservices
command: ["start-microservices.sh"]
environment:
- DB_HOSTNAME=immich_postgres
- DB_USERNAME=immich
- DB_PASSWORD=${cfg.dbPassword}
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=immich_redis
- TYPESENSE_HOST=immich_typesense
- DISABLE_REVERSE_GEOCODING=false
volumes:
- ${cfg.dataDir}/upload:/usr/src/app/upload
- ${cfg.dataDir}/library:/usr/src/app/library
- /etc/localtime:/etc/localtime:ro
depends_on:
- immich_postgres
- immich_redis
- immich_typesense
restart: unless-stopped
${if cfg.enableWatchtower then ''labels:
com.centurylinklabs.watchtower.enable: "true"'' else ""}
immich_postgres:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
container_name: immich_postgres
environment:
- POSTGRES_USER=immich
- POSTGRES_PASSWORD=${cfg.dbPassword}
- POSTGRES_DB=immich
volumes:
- ${cfg.dataDir}/postgres:/var/lib/postgresql/data
restart: unless-stopped
${if cfg.enableWatchtower then ''labels:
com.centurylinklabs.watchtower.enable: "true"'' else ""}
immich_redis:
image: redis:7-alpine
container_name: immich_redis
restart: unless-stopped
${if cfg.enableWatchtower then ''labels:
com.centurylinklabs.watchtower.enable: "true"'' else ""}
immich_typesense:
image: typesense/typesense:0.24.0
container_name: immich_typesense
environment:
- TYPESENSE_API_KEY=${cfg.typesenseApiKey}
- TYPESENSE_DATA_DIR=/data
volumes:
- ${cfg.dataDir}/typesense:/data
restart: unless-stopped
${if cfg.enableWatchtower then ''labels:
com.centurylinklabs.watchtower.enable: "true"'' else ""}
networks:
default:
name: immich_network
'';
# Systemd service to manage Immich docker-compose stack
systemd.services.docker-compose-immich = {
description = "Immich Docker Compose Stack";
after = [ "docker.service" "network-online.target" ];
wants = [ "network-online.target" ];
requiredBy = [ "multi-user.target" ];
path = [ pkgs.docker-compose pkgs.docker ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
WorkingDirectory = "/etc/compose/immich";
ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d";
ExecStop = "${pkgs.docker-compose}/bin/docker-compose down";
};
};
# Network firewall configuration (optional, enable if using firewall)
# networking.firewall.allowedTCPPorts = [ cfg.port ];
};
};
in
{
# Provide NixOS module
nixosModules.immich-docker = nixosModule;
# Provide the module as the default package
packages = flake-utils.lib.eachDefaultSystem (system:
{
immich-docker-module = nixosModule;
default = nixosModule;
}
);
};
}