thenug.net

gitea and portainer as a homelab gitops platform

every Docker stack on this homelab lives in a git repo on Gitea. Portainer deploys directly from those repos. the only way to change what's running is to change the compose file and push. no manual edits, no config drift.

the problem with ad-hoc compose management

the typical homelab Docker workflow: write a compose file, docker compose up -d, forget where the file lives, edit it in place, redeploy, repeat. six months later nothing is in source control, stacks differ from what you think they are, and recreating anything from scratch requires guesswork.

the pattern

Gitea is a lightweight self-hosted Git server. Portainer manages Docker stacks with a UI and API. together they work like this:

compose file → git push to Gitea
  └── Portainer pulls from Gitea repo
        └── deploys stack to Docker host
              └── runs containers

when a compose file needs to change — new service, updated image tag, environment variable — you edit the file locally, push to Gitea, and trigger a redeploy in Portainer. the git history is the audit trail.

how a deploy works

each stack in Portainer is linked to a specific Gitea repo and branch. Portainer pulls the docker-compose.yml from that repo and deploys it to the target Docker host. the setup supports multiple hosts — stacks on the GPU server are deployed from the same Portainer instance that manages the original server.

adding a new service to an existing stack:

  1. edit docker-compose.yml locally
  2. git push
  3. redeploy the stack in Portainer (one click or API call)

done. the running state matches the repo.

one important limitation

Portainer git stacks only pull the compose file to the remote agent. they don't clone the full repo. this means any config files referenced with relative paths (./config.yml) won't exist on the remote host — Docker will create an empty directory in their place.

config files that containers need must be placed on the host manually at absolute paths and mounted that way:

volumes:
  - /opt/mystack/config.yml:/etc/myapp/config.yml

relative paths work fine for single-host setups. for Portainer deploying to a remote agent, absolute paths only.

the one exception

Gitea itself can't be deployed this way. you can't store Gitea's compose file in a Gitea repo and have Portainer deploy it — if Gitea is down, Portainer can't reach the repo to redeploy it. Gitea runs as a Portainer stack managed through the web editor. everything else follows the git repo pattern.

what this enables

eli5

imagine every appliance in your house came with a blueprint stored in a filing cabinet (Gitea). a building manager (Portainer) has access to the filing cabinet and is responsible for making sure the actual appliances match the blueprints. when you want to change something, you update the blueprint and tell the manager to re-check the appliance. the manager reads the new blueprint and makes the appliance match it. if something breaks, you look at the filing cabinet to see what changed. the only appliance not in the filing cabinet is the filing cabinet itself — it has to exist before you can store anything in it.


← back to blog