- documentation/KBAs/: Created subdirectory for Knowledge Base Articles - documentation/SOPs/: Created subdirectory for Standard Operating Procedures - documentation/README.md: Updated to reflect new structure with section descriptions - Moved KBA-001 to KBAs/ folder - Created SOP-001 (Migrate Stack from UI to Git) in SOPs/ folder - Fixed all cross-reference links to use correct relative paths (../) Improves documentation organization by separating troubleshooting guides (KBAs) from procedural guides (SOPs), making it easier to navigate and maintain the knowledge base as it grows.
465 lines
12 KiB
Markdown
465 lines
12 KiB
Markdown
# SOP-001: Migrate Komodo Stack from UI-Defined to Git-Based
|
|
|
|
**Status:** Active
|
|
**Created:** April 11, 2026
|
|
**Last Updated:** April 11, 2026
|
|
**Owner:** Nathan Castaldi
|
|
**Applies To:** All Komodo-managed Docker Compose stacks
|
|
|
|
---
|
|
|
|
## Purpose
|
|
|
|
Convert an existing Komodo stack from UI-defined (manual configuration) to Git-based (GitOps workflow) to enable:
|
|
- Version control and change tracking
|
|
- Automated deployments via webhooks
|
|
- Easier rollback and disaster recovery
|
|
- Multi-environment consistency
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Required Access
|
|
|
|
- [ ] SSH access to the target node (e.g., `waldorf`, `heimdall`, `watchtower`)
|
|
- [ ] Komodo UI access (`komodo.castaldifamily.com`)
|
|
- [ ] Gitea repository access (`git.castaldifamily.com/nathan/homelab`)
|
|
- [ ] Git configured on your local machine
|
|
|
|
### Required Infrastructure
|
|
|
|
- [ ] Komodo Periphery has `/etc/komodo/repos` volume mounted (see [KBA-001](../KBAs/KBA-001-Komodo-GitOps-Stack-Deployment-Failures.md))
|
|
- [ ] Gitea webhooks configured and verified
|
|
- [ ] NFS mounts operational (if using shared storage)
|
|
|
|
---
|
|
|
|
## Pre-Migration Data Collection
|
|
|
|
Before making any changes, **capture the current stack configuration** from Komodo UI:
|
|
|
|
### Step 1: Export Stack Configuration
|
|
|
|
1. **Navigate to Komodo UI** → Stacks → Select your stack
|
|
2. **Copy the following information:**
|
|
|
|
| Field | Value | Notes |
|
|
|-------|-------|-------|
|
|
| **Stack Name** | _______________ | (e.g., `plex`, `tunarr`) |
|
|
| **Node Name** | _______________ | (e.g., `waldorf`, `heimdall`) |
|
|
| **Container Name(s)** | _______________ | From compose services |
|
|
| **Image(s)** | _______________ | **Critical:** Note exact tag |
|
|
| **Ports** | _______________ | Host:Container mappings |
|
|
| **Volumes** | _______________ | Full paths (host → container) |
|
|
| **Environment Variables** | _______________ | **Secrets go here, NOT in Git** |
|
|
| **Network Mode** | _______________ | (bridge/host/custom) |
|
|
| **Restart Policy** | _______________ | (unless-stopped/always/no) |
|
|
| **Labels** | _______________ | Traefik, Komodo, etc. |
|
|
| **Devices** | _______________ | GPU passthrough, /dev/dri, etc. |
|
|
|
|
3. **Copy the complete `compose.yaml`** from the stack editor
|
|
4. **Take a screenshot** of the environment variables section (contains secrets)
|
|
|
|
---
|
|
|
|
## Migration Procedure
|
|
|
|
### Step 2: Create Repository Directory Structure
|
|
|
|
On your **local machine** (or via Working Copy on iPad):
|
|
|
|
```bash
|
|
# Navigate to homelab repo
|
|
cd ~/homelab # Or your local path
|
|
|
|
# Create the directory structure
|
|
mkdir -p nodes/{node-name}/{stack-name}
|
|
|
|
# Example:
|
|
mkdir -p nodes/waldorf/sonarr
|
|
```
|
|
|
|
**Directory naming convention:**
|
|
- Node name: `heimdall`, `waldorf`, `watchtower`
|
|
- Stack name: Lowercase, matches service (e.g., `plex`, `tunarr`, `sonarr`)
|
|
|
|
---
|
|
|
|
### Step 3: Create compose.yaml
|
|
|
|
Create `nodes/{node-name}/{stack-name}/compose.yaml` with the **sanitized** configuration:
|
|
|
|
```yaml
|
|
services:
|
|
{service-name}:
|
|
image: {registry}/{image}:{tag} # ⚠️ NO 'v' prefix on tag
|
|
container_name: {container-name}
|
|
restart: unless-stopped # or 'always'
|
|
ports:
|
|
- {host-port}:{container-port}
|
|
environment:
|
|
- TZ=America/New_York
|
|
# ⚠️ DO NOT store passwords/tokens here
|
|
# Use Komodo UI Environment Variables instead
|
|
volumes:
|
|
- /mnt/appdata/{service}/config:/config
|
|
- /mnt/media/data:/data # If applicable
|
|
# Optional: GPU passthrough
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
count: 1
|
|
capabilities: [gpu]
|
|
# Optional: Device passthrough (Intel QuickSync, etc.)
|
|
devices:
|
|
- /dev/dri:/dev/dri
|
|
# Optional: Traefik labels
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.{service}.rule=Host(`{service}.castaldifamily.com`)"
|
|
- "komodo.managed=true"
|
|
```
|
|
|
|
**⚠️ Critical Configuration Rules:**
|
|
|
|
1. **Image Tags:**
|
|
- ✅ Correct: `image: chrisbenincasa/tunarr:1.2.11`
|
|
- ❌ Wrong: `image: chrisbenincasa/tunarr:v1.2.11` (no `v` prefix)
|
|
|
|
2. **Secrets Management:**
|
|
- ❌ **NEVER** commit passwords, API keys, or claim tokens to Git
|
|
- ✅ Use Komodo Stack Environment Variables for secrets
|
|
- ✅ Use placeholders in compose: `- PLEX_CLAIM=${PLEX_CLAIM}`
|
|
|
|
3. **Volume Paths:**
|
|
- Use absolute paths starting with `/mnt/appdata/`
|
|
- Ensure paths exist on the target node
|
|
- Match UID/GID permissions (usually `1000:1000`)
|
|
|
|
---
|
|
|
|
### Step 4: Commit and Push to Git
|
|
|
|
```bash
|
|
# Stage the new files
|
|
git add nodes/{node-name}/{stack-name}/
|
|
|
|
# Commit with descriptive message
|
|
git commit -m "feat(stacks): migrate {stack-name} to Git-based deployment
|
|
|
|
- Created compose.yaml for {node-name}/{stack-name}
|
|
- Extracted configuration from Komodo UI
|
|
- Secrets managed via Komodo Environment Variables"
|
|
|
|
# Push to Gitea
|
|
git push origin main
|
|
```
|
|
|
|
---
|
|
|
|
### Step 5: Reconfigure Stack in Komodo UI
|
|
|
|
1. **Navigate to Komodo UI** → Stacks → Select your stack
|
|
2. **Change stack type:**
|
|
- Click **Edit Stack** (gear icon)
|
|
- Change **Source Type** from `Manual` to `Git Repo`
|
|
|
|
3. **Configure Git settings:**
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| **Repo** | `homelab` |
|
|
| **Branch** | `main` |
|
|
| **Run Directory** | `nodes/{node-name}/{stack-name}` |
|
|
| **File Paths** | *(leave blank - uses compose.yaml by default)* |
|
|
|
|
4. **Re-add Environment Variables (Secrets):**
|
|
- Click **Environment Variables** tab
|
|
- Add back any secrets from your pre-migration notes:
|
|
```
|
|
PLEX_CLAIM=claim-xxxxxxxxx
|
|
SONARR_API_KEY=xxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
5. **Save Configuration**
|
|
|
|
---
|
|
|
|
### Step 6: Deploy and Verify
|
|
|
|
1. **Pull from Git:**
|
|
- Click **Pull Stack** button
|
|
- Wait for success notification
|
|
- Verify "Last pulled" timestamp updated
|
|
|
|
2. **Deploy Stack:**
|
|
- Click **Deploy Stack** button
|
|
- Monitor deployment logs in Komodo UI
|
|
- Wait for "Running" status
|
|
|
|
3. **Verify on Target Node:**
|
|
|
|
```bash
|
|
# SSH to the node
|
|
ssh chester@10.0.0.{node-ip}
|
|
|
|
# Check container status
|
|
docker ps | grep {container-name}
|
|
|
|
# Check logs for errors
|
|
docker logs {container-name} --tail 50
|
|
|
|
# Verify volumes mounted correctly
|
|
docker inspect {container-name} | grep -A 10 "Mounts"
|
|
|
|
# If GPU passthrough required:
|
|
docker exec {container-name} nvidia-smi
|
|
```
|
|
|
|
4. **Functional Testing:**
|
|
- Access the service via browser/app
|
|
- Verify data persistence (configs, databases)
|
|
- Test core functionality (playback, API calls, etc.)
|
|
|
|
---
|
|
|
|
## Verification Checklist
|
|
|
|
After migration, confirm:
|
|
|
|
- [ ] Container is running (`docker ps`)
|
|
- [ ] Service accessible via web UI/API
|
|
- [ ] Data persists across container restart
|
|
- [ ] Environment variables applied correctly
|
|
- [ ] GPU accessible (if applicable): `docker exec {name} nvidia-smi`
|
|
- [ ] Logs show no mount/permission errors
|
|
- [ ] Traefik routing works (if applicable)
|
|
- [ ] Auto-deployment triggers on Git push
|
|
|
|
---
|
|
|
|
## Rollback Procedure
|
|
|
|
If migration fails, revert to UI-defined stack:
|
|
|
|
1. **In Komodo UI:**
|
|
- Edit Stack → Change **Source Type** back to `Manual`
|
|
- Paste original compose.yaml from pre-migration backup
|
|
- Re-add environment variables
|
|
- Deploy stack
|
|
|
|
2. **On Target Node (Emergency):**
|
|
```bash
|
|
# Navigate to stack directory
|
|
cd /etc/komodo/stacks/{stack-name}
|
|
|
|
# Manually restore compose.yaml
|
|
nano compose.yaml # Paste backup content
|
|
|
|
# Restart manually
|
|
docker compose down
|
|
docker compose up -d
|
|
```
|
|
|
|
---
|
|
|
|
## Post-Migration Tasks
|
|
|
|
### Test Auto-Deployment
|
|
|
|
1. Make a minor change to the compose file (e.g., update comment)
|
|
2. Commit and push to Git
|
|
3. Verify Komodo auto-pulls and redeploys (check timestamps)
|
|
4. If auto-deploy doesn't work, manually click "Pull" → "Deploy"
|
|
|
|
### Update Documentation
|
|
|
|
- [ ] Add stack to node README (e.g., `nodes/waldorf/README.md`)
|
|
- [ ] Document any special configuration requirements
|
|
- [ ] Update infrastructure diagrams if necessary
|
|
|
|
---
|
|
|
|
## Common Issues
|
|
|
|
### Issue: "Image not found" after deploy
|
|
|
|
**Cause:** Docker tag has `v` prefix (e.g., `v1.2.11` instead of `1.2.11`)
|
|
|
|
**Fix:**
|
|
```yaml
|
|
# In compose.yaml, remove 'v' prefix
|
|
image: user/app:1.2.11 # Not v1.2.11
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Environment variables not applied
|
|
|
|
**Cause:** Secrets defined in Git-based compose.yaml instead of Komodo UI
|
|
|
|
**Fix:**
|
|
1. Remove secrets from `compose.yaml` in Git
|
|
2. Use placeholders: `- API_KEY=${API_KEY}`
|
|
3. Add actual values in Komodo UI → Stack → Environment Variables
|
|
|
|
---
|
|
|
|
### Issue: Pull Stack button doesn't update files
|
|
|
|
**Cause:** Known issue (see [KBA-001](../KBAs/KBA-001-Komodo-GitOps-Stack-Deployment-Failures.md))
|
|
|
|
**Workaround:**
|
|
```bash
|
|
# SSH to node
|
|
docker exec komodo-periphery-{node} sh -c \
|
|
'cp /etc/komodo/repos/homelab/nodes/{node}/{stack}/compose.yaml \
|
|
/etc/komodo/stacks/{stack}/compose.yaml'
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Permission denied on volume mounts
|
|
|
|
**Cause:** UID/GID mismatch between container and host directory
|
|
|
|
**Fix:**
|
|
```bash
|
|
# On target node, set correct ownership
|
|
sudo chown -R 1000:1000 /mnt/appdata/{service}/
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Secrets Management
|
|
|
|
| ❌ DO NOT | ✅ DO |
|
|
|-----------|-------|
|
|
| Commit passwords to Git | Use Komodo Environment Variables |
|
|
| Store API keys in compose.yaml | Use `${VAR_NAME}` placeholders |
|
|
| Hardcode claim tokens | Inject via Komodo UI |
|
|
|
|
### Example: Plex Claim Token
|
|
|
|
**Bad (in Git):**
|
|
```yaml
|
|
environment:
|
|
- PLEX_CLAIM=claim-sxFpsPTDzzF-9RZAxtUL # ❌ Exposed in Git
|
|
```
|
|
|
|
**Good (in Git):**
|
|
```yaml
|
|
environment:
|
|
- PLEX_CLAIM=${PLEX_CLAIM} # ✅ Placeholder
|
|
```
|
|
|
|
**In Komodo UI:**
|
|
- Environment Variables → Add: `PLEX_CLAIM=claim-sxFpsPTDzzF-9RZAxtUL`
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [KBA-001: Komodo GitOps Stack Deployment Failures](../KBAs/KBA-001-Komodo-GitOps-Stack-Deployment-Failures.md)
|
|
- [TECHNICAL_RUNBOOK.md](../TECHNICAL_RUNBOOK.md) - Infrastructure overview
|
|
- Repository Memory: `/memories/repo/active-tasks.md`
|
|
|
|
---
|
|
|
|
## Appendix: Example Migrations
|
|
|
|
### Example 1: Simple Service (No GPU)
|
|
|
|
**Stack:** Sonarr on Waldorf
|
|
|
|
```yaml
|
|
services:
|
|
sonarr:
|
|
image: lscr.io/linuxserver/sonarr:latest
|
|
container_name: sonarr
|
|
restart: unless-stopped
|
|
ports:
|
|
- 8989:8989
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=America/New_York
|
|
volumes:
|
|
- /mnt/appdata/sonarr:/config
|
|
- /mnt/media/tvshows:/tv
|
|
- /mnt/media/downloads:/downloads
|
|
```
|
|
|
|
---
|
|
|
|
### Example 2: GPU Transcoding Service
|
|
|
|
**Stack:** Plex on Waldorf (NVIDIA GTX 1060)
|
|
|
|
```yaml
|
|
services:
|
|
plex:
|
|
image: lscr.io/linuxserver/plex:latest
|
|
container_name: plex
|
|
network_mode: host
|
|
restart: unless-stopped
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=America/New_York
|
|
- VERSION=docker
|
|
- PLEX_CLAIM=${PLEX_CLAIM} # Set in Komodo UI
|
|
- NVIDIA_VISIBLE_DEVICES=all
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
|
|
volumes:
|
|
- /mnt/appdata/plex:/config
|
|
- /mnt/media/tvshows:/tv
|
|
- /mnt/media/movies:/movies
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
count: 1
|
|
capabilities: [gpu]
|
|
```
|
|
|
|
---
|
|
|
|
### Example 3: Traefik-Routed Service
|
|
|
|
**Stack:** Custom App on Heimdall (Behind Traefik)
|
|
|
|
```yaml
|
|
services:
|
|
myapp:
|
|
image: myregistry/myapp:2.1.0
|
|
container_name: myapp
|
|
restart: unless-stopped
|
|
ports:
|
|
- 3000:3000
|
|
environment:
|
|
- NODE_ENV=production
|
|
- DATABASE_URL=${DATABASE_URL} # Secret in Komodo UI
|
|
volumes:
|
|
- /mnt/appdata/myapp/data:/app/data
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.myapp.rule=Host(`myapp.castaldifamily.com`)"
|
|
- "traefik.http.routers.myapp.entrypoints=websecure"
|
|
- "traefik.http.routers.myapp.tls=true"
|
|
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"
|
|
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
|
|
```
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Tested On:** Komodo v2.1.2, Gitea v1.21
|
|
**Validation Status:** Production-Ready ✅
|