Adding a Service¶
How to onboard a new Docker Compose service into the homelab monorepo for explicit Komodo deployment and a generated Homepage tile. Pushing or merging the configuration does not start the Komodo relay.
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¶
set -euo pipefail
cp -r services/_template services/<service-name>
cd services/<service-name> || exit 1
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):
- Only for a new service with no encrypted source, create
.env.sops.yamlusingsops .env.sops.yamlon the operator machine. Use the example as a schema, not an unconditional copy over an existing secret file. Quote YAML values as strings. Save encrypted; commit only the encrypted source. - Review the required keys and consumers in
compose.yml, including passwords embedded in URLs. Choose a maintained service renderer where available. - Otherwise use the shared renderer below on the host from the service directory.
Replace
SERVICE_REQUIRED_KEYwith the actual required key and repeat--requirefor every required value; add--url-safe KEYfor URL-embedded passwords under the default restricted contract. This is a template, not a ready-to-run service contract. Do not rewrite an existing credential to fit it.
set -euo pipefail
cd /opt/homelab/services/<service-name> || exit 1
sudo -n python3 /opt/homelab/scripts/render-sops-env.py \
--source .env.sops.yaml --output .env --require SERVICE_REQUIRED_KEY || exit 1
Follow the rendering contract: reviewed
full checkout, Python 3, SOPS, authorized key context, private ignored staging,
strict validation and atomic promotion are mandatory, not optional. The default
host key and resulting .env are root-only; use the same authorized root context
for Compose. No deploy after a failed render, and do not force-add plaintext.
Unsupported dotenv characters fail closed; do not bypass this by redirecting SOPS.
Only LiteLLM callers use the reviewed --literal POSTGRES_PASSWORD and
--url-encode POSTGRES_PASSWORD=POSTGRES_PASSWORD_URLENCODED exception, retaining
both required keys and preserving the existing database credential. It does not
relax other values or placeholders. Do not copy those flags into another service
without a separately reviewed consumer contract; Phoenix retains --url-safe,
and Komodo/Firefly defaults are unchanged. See the
narrow exception and pre-cutover checks.
Source tests do not grant deployment approval or prove target-client compatibility.
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.
Choose the deployment-policy tag and corresponding host-local Procedure target
explicitly. List non-Compose deployment inputs too: native config_files needs an
explicit requires action and must not expose plaintext secrets. Source-built
applications need input-aware selection, not just Compose changes. Reuse the private
fingerprint pattern where appropriate; see the
Komodo capability audit.
Render through the maintained safe renderer before fingerprinting; predeploy only
verifies the inputs, and does not replace the secret/restore gates above.
8. Commit and push¶
Review and merge the service configuration to main. Pushes and merges do not
start the Komodo relay. Native GitHub reviewer/branch protection is not currently
configured; the environment label is not an enforced approval gate.
Coordinate the new resource's reviewed external bootstrap Sync and relay Read
grants before dispatching the general workflow. The API relay refuses missing or
unreadable declared resources, rather than guessing whether list results were
permission-filtered. See rollout and bootstrap.
Check the stack's deployment policy and Procedure coverage, then follow the
explicit main dispatch with
DEPLOY_HOMELAB and the terminal Update and health checks.
The relay waits for exact successful Sync and Procedure Updates; polling is not a
deployment fallback. Application acceptance remains separate from relay success.
The Homepage generator updates repository configuration; the tile becomes available
when that configuration reaches the running Homepage instance.
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 - [ ] Approved deployment has terminal successful Updates and verified service health
- [ ] 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