4.8 KiB
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
idinmetadata.json(lowercase kebab-case). -
Only folders that contain a valid
metadata.jsonare treated as services by the installer. -
After adding, editing, or removing a service, regenerate the index:
cd installer bun run catalogThis rewrites
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/<id>/, preserving subdirectories.
Reference those files from compose.volumes with relative paths:
"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:
"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.
compose (the service definition)
This object is the value you would normally put under a service key in docker-compose.yml. For
example:
{
"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:
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):
{
"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 incomposeas${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 adefaultis 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.