Skip to content

Komodo Deployment Guide

This repository uses Komodo for declarative container orchestration via Git-ops.

Architecture Overview

graph TD
    CORE["Komodo Core (Web UI)
https://komo.do / Self-hosted"] CORE -->|WebSocket :9120, REST API| PER["Periphery Agent
(one per server, via systemd)"] PER --> UNR["unraid (stacks)"] PER --> DEV["development (dev only)"] PER --> DB["database (disabled)"]

Directory Structure

.
├── komodo/
│   └── stacks/
│       ├── root_syncs/          # Core infrastructure
│       │   ├── resource_sync.toml   # One sync per category
│       │   ├── server.toml         # Server definitions
│       │   ├── alerter.toml        # Ntfy alerting
│       │   └── procedure.toml      # Scheduled tasks
│       │
│       ├── applications/          # App stacks (auto-generated)
│       ├── database/
│       ├── development/
│       ├── llm/
│       ├── monitoring/
│       ├── networking/
│       ├── orchestration/
│       └── storage/
├── stacks/                       # Docker Compose source of truth
│   └── {category}/
│       └── {service}/
│           └── compose.yaml
└── dockerfiles/                  # Custom build Dockerfiles

Resource Sync Flow

graph LR
    A[Git Repo] -->|push| B[Webhook]
    B -->|trigger| C[Komodo Core]
    C -->|sync| D[resource_sync.toml]
    D -->|diff| E[Pending Changes]
    E -->|manual confirm| F[Deploy Stack]

Each category has its own resource_sync pointing to its TOML folder:

Sync Name Path Purpose
root-syncs ./komodo/stacks/root_syncs Infrastructure (servers, alerter, procedures)
applications ./komodo/stacks/applications User-facing applications
database ./komodo/stacks/database Database services
development ./komodo/stacks/development Dev environments (Coder, Gitea, etc.)
llm ./komodo/stacks/llm LLM services (Ollama, LiteLLM, etc.)
monitoring ./komodo/stacks/monitoring Observability stack
networking ./komodo/stacks/networking Network services (Pi-hole, AdGuard, etc.)
orchestration ./komodo/stacks/orchestration Workflow engines (Airflow, n8n, Dagster)
storage ./komodo/stacks/storage Storage services (MinIO, SFTPGo, etc.)

Adding a New Stack

1. Create Docker Compose

mkdir -p stacks/{category}/{service}
# Write compose.yaml

2. Create Komodo TOML

Create komodo/stacks/{category}/{service}.toml manually. Copy from an existing stack as template:

[[stack]]
name = "service-name"
deploy = true
tags = ["server:unraid", "category:{category}"]

[stack.config]
server = "unraid"
linked_repo = "docker-compose"
run_directory = "/etc/komodo/repos/docker-compose/stacks/{category}/{service}/"
file_paths = ["compose.yaml"]

See deployment-guide.md for the full template with all options.

3. Customize Tags

Edit the TOML and add meaningful tags:

[[stack]]
name   = "my-service"
deploy = true
tags   = [
  "server:unraid",
  "category:applications",
  "saas:tailscale",     # Expose via Tailscale
  "selfhost:photo",     # Self-hosted category
]

4. Common Tag Conventions

Tag Pattern Example Meaning
server:{name} server:unraid Target server
category:{name} category:applications Stack category
saas:{name} saas:tailscale Expose via SaaS (Tailscale)
selfhost:{name} selfhost:photo Self-hosted replacement for SaaS
app:{name} app:immich Application identifier

Stack TOML Reference

[[stack]]
name   = "service-name"
deploy = true
tags   = ["server:unraid", "category:applications"]

[stack.config]
server = "unraid"

# Update behavior
poll_for_updates         = false   # Check but don't auto-deploy
auto_update              = true    # Auto-deploy on new image digest
destroy_before_deploy    = true    # Remove container before redeploy
auto_update_all_services = false   # Only update changed services

# Source
linked_repo  = "docker-compose"    # Komodo repo name
run_directory = "./stacks/applications/my-service/"
file_paths   = ["compose.yaml"]
additional_env_files = ["compose.env"]  # Optional env file

# Pre-deploy script (optional)
pre_deploy.command = """
#!/usr/bin/env bash
set -eu pipefail
echo "Running before deploy..."
"""

# Environment - use [[VAR]] for secrets from Komodo
environment = """
TZ=Asia/Ho_Chi_Minh
DB_PASSWORD=[[DB_PASSWORD]]
"""

Environment Variables

Secret Interpolation

# In TOML, secrets use [[VAR]] syntax:
environment = """
DB_PASSWORD=[[DB_PASSWORD]]
API_KEY=[[API_KEY]]
"""

# Komodo resolves these from:
# 1. Komodo Variables UI (for non-secret values)
# 2. Core config file secrets block
# 3. Periphery config file secrets block

compose.env Usage

For local overrides, create compose.env in the stack directory:

# stacks/my-service/compose.env
DB_HOST=localhost
DB_PORT=5432

Server Configuration

Servers are defined in komodo/stacks/root_syncs/server.toml:

[[server]]
name = "unraid"
description = "Main NAS server"
tags = ["server:unraid", "category:root_syncs"]

[server.config]
address = "http://100.68.251.84:8120"
enabled = true

[[server]]
name = "development"
description = "Dev VM"
tags = ["server:development", "category:root_syncs"]

[server.config]
address = "http://100.126.172.96:8120"
enabled = true

Alerting

Alerter is configured in komodo/stacks/root_syncs/alerter.toml:

[[alerter]]
name = "ntfy"
[alerter.config]
enabled = true
endpoint.type = "Ntfy"
endpoint.params.url = "http://development:9282/komodo"

Attaching Alerter to Stack

Add alerter tag to route alerts:

tags = ["server:unraid", "category:applications", "alerter:ntfy"]

Deployment Dependencies

Use after to order deployments across services:

[[stack]]
name = "postgres"
deploy = true
[stack.config]
# ...

[[stack]]
name = "myapp"
deploy = true
after = ["postgres"]  # Deploy after postgres
[stack.config]
# ...

Development Workflow

Local Development Loop

# 1. Edit compose.yaml locally
vim stacks/applications/myapp/compose.yaml

# 2. Test locally
cd stacks/applications/myapp
docker compose up -d

# 3. Commit and push
git add stacks/applications/myapp/
git commit -m "feat: update myapp"
git push

# 4. Komodo syncs via webhook and shows pending changes
# 5. Review in Komodo UI and confirm deploy

Forcing a Redeploy

# Via Komodo CLI
km deploy stack myapp

# Or via Procedure (run immediately)
km run action trigger-redeploy -y

Maintenance

Backup

Backups run daily via Backup Core Database procedure:

[[procedure]]
name = "Backup Core Database"
config.schedule = "0 0 1 * * *"  # Daily at 01:00

Rotate Periphery Keys

km update server unraid auto-rotate-keys true

Update All Stacks

# Via sync-all procedure
km run procedure sync-all

Network Architecture

graph TD
    EXT["External (Internet)"] -->|HTTPS| TS["Tailscale VPN Network"]
    TS --> NOTE["Domain pattern: {service}.{MACHINE_NAME}.tailXXXX.ts.net
Internal: {service}.home → Caddy (192.168.100.10)"]

CI/CD Integration

Custom Docker images are built via Komodo Build resources and pushed to the self-hosted Gitea registry:

graph LR
    DF["stacks/{category}/{service}/Dockerfile"] --> KB[Komodo Build]
    KB --> REG["git.minhluc.info/lucndm/{service}:latest"]
    REG --> DEPLOY[Stack deploy]

The build pipeline is configured in Komodo UI (Build resources), not in this repository. Builds are triggered automatically when the Dockerfile changes in the linked repo.

See Custom Image Build Workflow for the full pattern.

Troubleshooting

Periphery Not Connecting

# Check periphery status
sudo systemctl status periphery

# View logs
sudo journalctl -u periphery -f

# Restart
sudo systemctl restart periphery

Stack Won't Deploy

# Check sync status
km x commit root-syncs  # Force sync

# View pending changes
# In Komodo UI > Resource Sync > {sync-name}

View Container Logs

km exec <container> bash -s <server>

Quick Reference

Task Command
Deploy a stack km deploy stack <name>
View stack logs km-logs -S <name>
Sync resources km x commit <sync-name>
Run procedure km run procedure <name>
Set variable km set var <KEY> <value>
SSH to server km ssh <server>
Exec into container km exec <container> bash