Migrating Five Homelab Sites to Git-Linked Komodo Stacks
For most of this homelab's life, deploying a site update meant SSH-ing in, pulling code, running a build script, and restarting a container by hand. It worked, but it didn't scale past one or two sites, and it left no real audit trail of what got deployed when. This month I moved all five of my personal and ministry websites onto Komodo-managed Stacks, so a deploy is now a git pull plus a rebuild, triggered from Komodo's UI or API instead of a shell script.
Where Things Started#
Komodo was already running on this homelab as a container orchestration layer, but the sites themselves weren't actually using it the way it's meant to be used. Several of the /etc/komodo/stacks/ directories on docker01 contained old, stale content left over from before earlier migrations, like a leftover nginx and static HTML setup for pearsondw.com from before it moved to Next.js, completely disconnected from what was actually running in production. Meanwhile the real deploy path for that site was a separate deploy.sh script building an image and SCP-ing it into place from an entirely different directory.
Each site had its own quirks, but the goal was the same for all five: point Komodo at the GitHub repo, let it clone and build directly, and stop hand-running deploy scripts.
The Working Pattern#
Once I had one site (dkpearson.com) fully migrated as a pilot, the rest followed a repeatable sequence:
- Register a git provider account once. A single GitHub PAT registered against Komodo covers every stack that needs to pull from
github.com/dwpdkp. - Create the Stack, pointing at the right repo, branch, and compose file path.
- Turn on
run_build: true. This defaults to off, which means Komodo will happilydocker compose upon deploy without ever rebuilding the image. Any site usingbuild: .in its compose file needs this flag on, or code changes silently never take effect. - Pull before deploying.
PullStackjust clones, with zero impact on the running container, so it's safe to validate a stack's git wiring before touching anything live.DeployStackis the step that actually rebuilds and recreates the container. - Poll
GetUpdatefor the operation's logs and status rather than trusting a single early check. Catching an operation mid-flight with no logs yet is easy to misread as a hang when it's really just still running.
The Real Blocker Wasn't Git or the API#
The thing that actually slowed this migration down had nothing to do with Komodo's git integration. Komodo's periphery agent clones into /etc/komodo/stacks/<name>/ running as root, and for three of the five sites that directory already existed, owned by a different user from earlier manual deploys. Git's own "detected dubious ownership" safety check blocks a clone into a directory it doesn't trust, and the failure surfaces buried in a stage's stderr, not in any top-level error message that actually points at the cause.
The fix, once identified, was simple and repeatable: back up the existing directory, then re-chown it so the periphery agent's user can write to it, before ever attempting the first pull. Worth checking for on any future stack too, since it's an easy thing to trip over blind.
The env_file Gotcha#
The trickiest site to migrate was pearsonflighttraining.com, which referenced a gitignored .env.local file directly in its compose file's env_file: directive. Since that file is (correctly) never committed, Komodo's fresh clone never has it, and docker compose config fails validation before Komodo's own environment-writing step ever runs. Setting environment variables through Komodo's own stack config doesn't fix this, because that mechanism is separate from the literal env_file: path baked into the compose YAML, which Docker Compose resolves against the working directory at parse time.
That site's production .env.local turned out to be empty, so the fix was just removing the env_file: line entirely. The real test came with ignitethesparkmovement.org, which has genuine production secrets: Stripe keys, Sanity tokens, database credentials, an email API key. There the fix was pointing env_file: at .env, matching the filename Komodo actually writes when it pushes environment variables into a stack. Once that matched, the "Write Environment File" stage started appearing and succeeding in the pull logs, and the secrets never had to touch a terminal session or get typed anywhere by hand, since I scripted pulling them from the live production file and pushing them straight into Komodo's API.
Verifying the Riskiest One#
ignitethesparkmovement.org runs a Postgres-backed storefront with real orders and subscriptions, so migrating it wasn't something to just deploy and hope. Before cutting over, I confirmed the exact live volume name via docker volume ls and pinned it explicitly in the compose file with external: true, which makes compose refuse to start at all if that exact volume doesn't already exist rather than silently creating an empty one on any naming mismatch. After the deploy, I checked the container's mount against that expected volume name, confirmed no duplicate volume had been created, and queried the database directly to confirm all six tables and their data were untouched.
Where It Landed#
All five sites, dkpearson.com, dpearsonministries.com, pearsondw.com, pearsonflighttraining.com, and ignitethesparkmovement.org, are now git-linked Komodo Stacks. A deploy today looks like: push to main on GitHub, PullStack to validate the clone, DeployStack to rebuild and recreate the container. No more manually building images on a laptop, no more SCP, and a real history of every deploy in Komodo's own update log instead of scattered shell history.
For more on how these sites are hosted, see Exposing Homelab Services Securely with Cloudflare Tunnel.