Adding a Service¶
How to onboard a new Docker Compose service into the homelab monorepo so that Komodo deploys it automatically and Homepage picks up a tile.
Prerequisites¶
- Docker and Docker Compose installed on the target host
- The Traefik stack is running (it owns the
traefikDocker network) - Komodo is running and connected to the host
Steps¶
1. Copy the template¶
2. Edit compose.yml¶
- Replace all
REPLACE_MEplaceholders with real values - Set the correct image, container name, and ports
- Configure Traefik labels with the desired subdomain (
<name>.infra.realemail.app) - Add
traefik.http.routers.<name>.middlewares=authentik@file(ADR-002 SSO — see authentik-cross-host-sso.md) - Add volumes for persistent data if needed
- Ensure the service joins the
traefiknetwork (external)
3. Configure secrets¶
If the service needs secrets (API keys, passwords, DB credentials):
- Copy
.env.sops.yaml.exampleto.env.sops.yamland fill in real values - Encrypt in WSL:
sops -e -i services/<service-name>/.env.sops.yaml - Commit and push the encrypted file
- On the host, decrypt SOPS YAML to a dotenv-format
.envwith restrictive permissions:
umask 077
SOPS_AGE_KEY_FILE=/etc/homelab/age-key.txt \
sops --decrypt --output-type dotenv .env.sops.yaml > .env.tmp
mv .env.tmp .env
Prefer a service-specific renderer with cleanup traps, validation, and atomic rename. See secrets runbook.
4. Update inventory (if applicable)¶
Create inventory/services/<service-name>.yaml for each new Komodo-managed
service. Existing stacks represented by legacy host links can migrate
incrementally:
id: example
name: Example
status: active
host: infra-services
port: 8080
url: https://example.infra.realemail.app
backup:
policy: tier-2
monitoring:
healthcheck: /healthz
alert_owner: homelab
documentation: docs/services/example.md
tags: [infrastructure]
Active and decommissioning services require host. An active service with a
url becomes a Homepage tile. Tags monitoring or observability place it in
the Monitoring category; infrastructure or ops place it in Infrastructure.
Then:
- Add or update the inventory entry.
- Run generators to update downstream files:
uv run python inventory/generators/render-discovery-inventory.py
uv run python inventory/generators/render-ansible.py
uv run python inventory/generators/render-homepage.py
uv run python inventory/generators/render-doc-stubs.py
uv run python inventory/generators/render-prometheus.py
uv run python inventory/generators/render-diagram.py
uv run python inventory/generators/render-dns-rewrites.py
Before push, run the same generators with --check; CI fails on generator drift.
5. Update backup policy¶
Edit backup.yml in the service directory:
- Set the correct tier (1 = critical, 2 = nice-to-have, 3 = replaceable)
- Set
hoststo the Docker host(s) where the listed paths exist - List the paths that need backing up
- Add pre/post hooks if the service needs to quiesce before backup
Per-service services/<name>/backup.yml is the runtime manifest. Update
restore.md when a service needs non-obvious restore steps.
6. Write the README¶
Fill in the service README with:
- What the service does
- Quick reference table (image, port, URL)
- Any manual setup steps
- Troubleshooting tips
7. Add Komodo resources¶
Add the stack to services/komodo/resources.toml so Komodo reconciles it from
/opt/homelab/services/<service-name>. Use compose-only file_paths; the
komodo-resources-check CI job validates referenced paths and TOML syntax.
8. Commit and push¶
Komodo detects the push (via webhook or polling) and deploys the stack automatically. The Homepage tile appears after the generator runs.
DNS¶
New services on infra-services use *.infra.realemail.app subdomains. The
static AdGuard wildcard in services/adguard/infra-dns-rewrites.yaml already
maps that zone to 192.168.6.17; the inventory DNS generator manages host
records under *.lab.local, not individual service hostnames. Use Cloudflare
DNS only if a service is intentionally public.
Checklist¶
- [ ]
compose.ymlhas noREPLACE_MEplaceholders - [ ]
.env.sops.yamlis encrypted (pre-commit hook verifies this) - [ ]
backup.ymltier is set correctly - [ ] README documents the service
- [ ]
inventory/services/<name>.yamlvalidates and generates a Homepage tile - [ ] Generators run cleanly (
--checkpasses) - [ ]
services/komodo/resources.tomlincludes the stack and passes CI - [ ] Service starts with
docker compose up -don the target host - [ ] Traefik routes traffic correctly to the service
- [ ] Traefik router includes
authentik@filemiddleware (infra-services only) - [ ] Authentik provider covers the new hostname (or domain-level provider)
- [ ] Prometheus target or scrape config is added when the service exposes metrics
- [ ] Restore runbook covers any state that cannot be recreated from git/SOPS