Skip to content

Instantly share code, notes, and snippets.

@MrModest
Created May 26, 2025 10:16
Show Gist options
  • Select an option

  • Save MrModest/221e4a051bf680cda02fa5e652090d55 to your computer and use it in GitHub Desktop.

Select an option

Save MrModest/221e4a051bf680cda02fa5e652090d55 to your computer and use it in GitHub Desktop.
HCL as homelab declarative language
locals {
stack_name = "immich"
}
resource "docker_compose" "stack_immich" {
name = var.stack_name
network {
name = var.reverse_proxy_network
external = true
}
services = [
docker_compose_service.server,
docker_compose_service.machine_learning
docker_compose_service.redis,
docker_compose_service.database,
]
}
resource "docker_compose_service" "server" {
image = "ghcr.io/immich-app/immich-server"
imageTag = var.app_version
container_name = "${var.stack_name}-server"
hostname = "${var.stack_name}-server"
user = "${var.puid}:${var.pgid}"
restart = "unless-stopped"
envs = {
TZ = var.timezone
REDIS_HOSTNAME = docker_compose_service.redis.hostname
}
volume {
container_path = "/usr/src/app/upload"
host_path = var.volumes.library
}
volume {
container_path = "/usr/src/app/external"
host_path = var.volumes.external_library
}
requires {
service = docker_compose_service.database
condition = "service_healthy"
}
requires {
service = docker_compose_service.redis
condition = "service_healthy"
}
}
resource "docker_compose_service" "machine-learning" {
image = "ghcr.io/immich-app/immich-machine-learning"
imageTag = var.app_version
container_name = "${var.stack_name}-machine-learning"
hostname = "${var.stack_name}-machine-learning"
user = "${var.puid}:${var.pgid}"
restart = "unless-stopped"
envs = {
TZ = var.timezone
MPLCONFIGDIR = "/usr/src/app/matplotlib"
}
resources = {
limits = {
cpus = "2"
}
}
volume {
container_path = "/cache"
host_path = var.volumes.ml_cache
}
volume {
container_path = "/usr/src/app/matplotlib"
host_path = var.volumes.matplotlib
}
}
resource "docker_compose_service" "redis" {
image = "docker.io/redis"
imageTag = var.redis_version
container_name = "${var.stack_name}-redis"
hostname = "${var.stack_name}-redis"
user = "${var.puid}:${var.pgid}"
restart = "unless-stopped"
volume {
container_path = "/data"
host_path = var.volumes.redis_data
}
healthcheck {
test = ["CMD-SHELL", "redis-cli ping || exit 1"]
interval = "10s"
timeout = "5s"
retries = 5
}
}
resource "docker_compose_service" "database" {
image = "docker.io/tensorchord/pgvecto-rs"
imageTag = var.postgres_version
container_name = "${var.stack_name}_postgres"
user = var.app_user
restart = "unless-stopped"
envs = {
POSTGRES_PASSWORD = var.db_password
POSTGRES_USER = var.db_username
POSTGRES_DB = var.db_name
POSTGRES_INITDB_ARGS = "--data-checksums"
}
volume {
container_path = "/var/lib/postgresql/data"
host_path = var.volumes.db_data
}
healthcheck = {
test = [
"CMD-SHELL",
<<-EOT
pg_isready --dbname="$$POSTGRES_DB" --username="$$POSTGRES_USER" || exit 1;
Chksum="$$(
psql --dbname="$$POSTGRES_DB" --username="$$POSTGRES_USER" \
--tuples-only --no-align \
--command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database'"
)";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = "0" ] || exit 1
EOT
]
interval = "5m"
start_interval = "30s"
start_period = "5m"
}
command = [
"postgres",
"-c", "shared_preload_libraries=vectors.so",
"-c", "search_path=\"$$user\", public, vectors",
"-c", "logging_collector=on",
"-c", "max_wal_size=2GB",
"-c", "shared_buffers=512MB",
"-c", "wal_compression=on"
]
}
output "server_port" {
type = number
}
variable "app_version" {
type = string
default = "latest"
}
variable "puid" {
type = string
}
variable "pgid" {
type = string
}
variable "volumes" {
type = object({
library = string
external_library = string
})
}
@MrModest
Copy link
Author

MrModest commented Sep 19, 2025

another try

variable "user" {
  type = object({
    uid = number
    gid = number
  })
}

variable "mounts" {
  type = object({
    app_data = string
  })
}

variable "proxy_network" {
  type = string
}

variable "meili_master_key" {
  type      = string
  sensitive = true
}

locals {
  name              = "hoarder"
  server_tag           = "0.26.0"
  chrome_tag        = "123"
  meilisearch_tag   = "v1.11.1"
  hoarderbot_tag    = "0.1.0"
}

volume "app_data" {
  host_path = "${var.mounts.app_data}/data"
  permissions = {
    owner = var.user.uid
    group = var.user.gid
    mode  = "0755"
  }
}

volume "meilisearch" {
  host_path = "${var.mounts.app_data}/meilisearch"
  permissions = {
    owner = var.user.uid
    group = var.user.gid
    mode  = "0755"
  }
}

container "server" {
  name     = "${local.name}_server"
  hostname = "${local.name}_server"
  image    = "ghcr.io/hoarder-app/hoarder"
  tag      = local.server_tag
  user     = "${var.user.uid}:${var.user.gid}"
  restart  = "unless-stopped"

  volumes = [{
    host      = volume.app_data.host_path
    container = "/data"
  }]

  envs = {
    MEILI_ADDR        = container.meilisearch.addresses[0]
    BROWSER_WEB_URL   = container.chrome.addresses[0]
    DATA_DIR          = "/data"
    MAX_ASSET_SIZE_MB = "2048"
  }

  networks = [
    var.proxy_network
  ]
}

container "chrome" {
  name     = "${local.name}_chrome"
  hostname = "${local.name}_chrome"
  image    = "gcr.io/zenika-hub/alpine-chrome"
  tag      = local.chrome_tag
  user     = "${var.user.uid}:${var.user.gid}"
  restart  = "unless-stopped"

  command = [
    "--no-sandbox",
    "--disable-gpu",
    "--disable-dev-shm-usage",
    "--remote-debugging-address=0.0.0.0",
    "--remote-debugging-port=9222",
    "--hide-scrollbars"
  ]
}

container "meilisearch" {
  name     = "${local.name}_meilisearch"
  hostname = "${local.name}_meilisearch"
  image    = "getmeili/meilisearch"
  tag      = local.meilisearch_tag
  user     = "${var.user.uid}:${var.user.gid}"
  restart  = "unless-stopped"

  volumes = [{
    host      = volume.meilisearch.host_path
    container = "/meili_data"
  }]

  envs = {
    MEILI_NO_ANALYTICS = "true"
    MEILI_MASTER_KEY   = var.meili_master_key
  }
}

container "bot" {
  name     = "${local.name}_bot"
  hostname = "${local.name}_bot"
  image    = "ghcr.io/madh93/hoarderbot"
  tag      = local.hoarderbot_tag
  user     = "${var.user.uid}:${var.user.gid}"
  restart  = "unless-stopped"

  envs = {
    HOARDERBOT_HOARDER_URL = container.app.addresses[0]
  }
}

stack "hoarder" {
  networks  = [{
      external: true
      name: var.proxy_network
  }]
  services = [
    container.app,
    container.chrome,
    container.meilisearch,
    container.bot
  ]

  proxy = [{
    service   = container.app
    subdomain = local.name
  }]
}
provider "onepassword" {
    url                   = "http://localhost:8080"
    token                 = "CONNECT_TOKEN"
    service_account_token = "SERVICE_ACCOUNT_TOKEN"
    account               = "ACCOUNT_ID_OR_SIGN_IN_ADDRESS"
    op_cli_path           = "OP_CLI_PATH"
}

data "onepassword_vault" "homelab" {
  uuid = "7fcff7da-20f9-4635-956e-0bb10b3c6c0a"
}

data "onepassword_item" "meilisearch_master_key" {
  vault = data.onepassword_vault.homelab
  title = "meilisearch_master_key"
}

locals {
    user = {
        uid = 1000
        gid = 1000
    }

    app_data_root = {
        fast = var.config.stack.path.fast-safe.apps_data_root
        slow = var.config.stack.path.slow-safe.apps_data_root
    }

    proxy_network = "nginxproxy"
}

module "hoarder" {
    source = "./modules/stacks/hoarder"

    user = local.user

    mounts = {
        app_data = "${local.app_data_root.fast}/hoarder"
    }

    network = local.proxy_network

    meili_master_key = data.onepassword_item.meilisearch_master_key.password
}

module "immich" {
    source = "./modules/stacks/immich"

    user = local.user

    mounts = {
        app_data = "${local.app_data_root.fast}/immich"
        media = "${local.app_data_root.slow}/immich"
    }

    network = local.proxy_network
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment