# services One folder per service. Each folder must contain a `metadata.json` describing the service (including the Docker Compose definition used to deploy it). The folder may also contain any extra files the service needs — a `Caddyfile`, a `Dockerfile`, static assets — which the installer copies into the deploy directory. ## Folder conventions - The folder name **must** equal the service `id` in `metadata.json` (lowercase kebab-case). - Only folders that contain a valid `metadata.json` are treated as services by the installer. - After adding, editing, or removing a service, regenerate the index: ```bash cd installer bun run catalog ``` This rewrites [`catalog.json`](catalog.json), which the installer uses as a one-request listing. ## Extra files A service folder can contain files beyond `metadata.json` — for example the `allprox` service ships a `Caddyfile` and a `portal/index.html`. On install/update the installer copies the whole folder (except `metadata.json`) into `~/.homelab/services//`, preserving subdirectories. Reference those files from `compose.volumes` with **relative** paths: ```json "volumes": [ "./Caddyfile:/etc/caddy/Caddyfile:ro", "./portal:/srv/portal:ro" ] ``` Because the generated `docker-compose.yml` lives in the same directory, `docker compose` resolves the `./...` paths against it. ## Shared networks Services that need to talk to each other (e.g. `allprox` → `authelia` → `lldap`) join an external Docker network. Declare it at the top level of `metadata.json` and attach the service to it: ```json "networks": { "homelab": { "external": true } }, "compose": { "networks": ["homelab"] } ``` The installer creates the external network automatically (if it doesn't already exist) before running `docker compose up`. ## `metadata.json` reference | Field | Required | Type | Description | | --- | --- | --- | --- | | `id` | ✅ | string | Unique kebab-case id, equal to the folder name. | | `name` | ✅ | string | Human-readable name shown in the TUI. | | `description` | ✅ | string | Short description. | | `version` | ✅ | string | Version string for change detection on update. | | `compose` | ✅ | object | A Docker Compose **service** definition (`image`, `ports`, `volumes`, `environment`, …). The installer wraps it in a generated `docker-compose.yml`. | | `volumes` | — | object | Optional top-level named volumes to declare. | | `networks` | — | object | Optional top-level networks to declare. | | `env` | — | array | Variables the installer resolves for you (see below). | | `category` | — | string | Grouping shown as a hint in the TUI. | | `tags` | — | string[] | Free-form tags. | | `icon` | — | string | URL to an icon. | | `author` | — | string | Upstream author. | | `license` | — | string | License of the deployed software. | | `homepage` | — | string | Project homepage URL. | | `documentation` | — | string | Documentation URL. | | `dependsOn` | — | string[] | Ids of services that should be installed first (informational). | | `notes` | — | string | Free-form notes shown by `info`. | A machine-readable JSON Schema is available at [`docs/metadata-schema.json`](../docs/metadata-schema.json). ### `compose` (the service definition) This object is the value you would normally put under a service key in `docker-compose.yml`. For example: ```json { "image": "ghcr.io/example/myapp:1.0.0", "container_name": "myapp", "restart": "unless-stopped", "ports": ["8080:8080"], "volumes": ["myapp_data:/data"], "environment": ["TZ=${TZ}"] } ``` The installer turns it into: ```yaml services: myapp: image: ghcr.io/example/myapp:1.0.0 # ... ``` Pin images to a tag so updates are predictable; the installer compares `version` to decide whether an update is available. ### `env` (interactive variables) Each entry describes a variable the installer should collect (and write to `.env`): ```json { "name": "WEBPASSWORD", "label": "Web admin password", "description": "Password for the web UI", "default": "change-me", "required": true, "secret": true, "options": [] } ``` - `name` (required) — the variable name. Reference it in `compose` as `${NAME}`. - `label` / `description` — shown when prompting. - `default` — used when the user accepts the default or runs non-interactively. - `required` — a missing value is an error in non-interactive mode unless a `default` is set. - `secret` — mask input at the prompt (e.g. passwords). - `options` — if provided, the installer offers a fixed choice list instead of free text. - `generate` — generate a random 64-char hex secret instead of prompting (for JWT/session secrets). Docker Compose automatically reads the `.env` written next to the generated compose file, so `${NAME}` references resolve at `docker compose up` time.