Created
May 26, 2025 10:16
-
-
Save MrModest/221e4a051bf680cda02fa5e652090d55 to your computer and use it in GitHub Desktop.
HCL as homelab declarative language
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| locals { | |
| stack_name = "immich" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| output "server_port" { | |
| type = number | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| variable "app_version" { | |
| type = string | |
| default = "latest" | |
| } | |
| variable "puid" { | |
| type = string | |
| } | |
| variable "pgid" { | |
| type = string | |
| } | |
| variable "volumes" { | |
| type = object({ | |
| library = string | |
| external_library = string | |
| }) | |
| } |
Author
Author
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
Previous idea with k8s-like config: https://gist.github.com/MrModest/cca4d3f414cec06c88a374a1c1feb9e6