9.2 KiB
Automated Development Systems
How we connect self-hosted Gitea, Mattermost, CI, and a lightweight PaaS into a continuous development pipeline where code goes from PR to production with minimal human intervention.
The Hub: Self-Hosted Gitea
Gitea is the coordination center. Every repo, issue, PR, and code review lives here. We self-host it because:
- Full API access for automation (the agent has its own Gitea user with API token)
- Gitea Actions for CI (compatible with GitHub Actions syntax)
- Webhooks on every repo event
- No vendor lock-in, no rate limits, no surprise pricing changes
- Complete control over visibility (public/private per-repo)
The agent (OpenClaw) has its own Gitea account (clawbot) separate from the
human user. This matters because:
- The agent's commits, comments, and PR reviews are clearly attributed
- The human can @-mention the agent in issues to assign work
- Assignment is unambiguous — issues assigned to
clawbotare the agent's queue, issues assigned to the human are theirs - The agent can be given write access per-repo (some repos it can push to directly, others it must fork and PR)
- API rate limits and permissions are independent
Real-Time Activity Feed: Gitea → Mattermost
Every repo has a Gitea webhook that sends all activity (pushes, PRs, issues, comments, reviews, CI status) to a channel in our self-hosted Mattermost instance. This creates a real-time feed where everyone — human and agent — can see what's happening across all projects without cross-communication overhead.
The agent also has its own Mattermost bot user (@claw), separate from the
human. This means:
- The agent posts status updates to dedicated channels (
#gitfor Gitea work,#clawfor general work narration) - The human's DMs stay clean — only direct alerts and responses
- In group channels, it's clear who said what
- The agent can be @-mentioned in any channel
Channel Architecture
A practical setup:
- #git — Real-time Gitea webhook feed (all repos) + agent's work status updates. Everyone sees commits, PRs, reviews, CI results as they happen.
- #claw — Agent's internal work narration. Useful for debugging what the agent is doing, but notifications muted so it doesn't disturb anyone.
- DM with agent — Private conversation, sitreps, sensitive commands
- Project-specific channels — For coordination with external collaborators
The webhook feed in #git means nobody needs to check Gitea's notification inbox manually. PRs, reviews, and CI results flow into a channel that's always open. The agent monitors the same feed (via its notification poller) and can react to events in near-realtime.
CI: Gitea Actions
Every repo has a CI workflow in .gitea/workflows/ that runs on push. The
standard workflow is one line:
- name: Build and check
run: docker build .
Because the Dockerfile runs make check (which runs tests, linting, and
formatting checks), a successful Docker build means everything passes. A failed
build means something is broken. Binary signal, no ambiguity.
PR Preview Deployments
For web projects (like a blog or documentation site), CI can go further than pass/fail. When a PR is opened against a site repo, CI can:
- Build the site from the PR branch
- Deploy it to a preview URL
- Post the preview URL as a comment on the PR or drop it into a Mattermost channel
This lets reviewers see the actual rendered result before merging, not just the code diff. For a Jekyll/Hugo blog, this means you can see how a new post looks on the real site layout, on mobile, with real CSS — before it goes live.
Deployment: µPaaS
µPaaS is a lightweight, self-hosted platform-as-a-service that auto-deploys Docker containers when code changes. It exists because:
- Full PaaS platforms (Kubernetes, Nomad, etc.) are massive overkill for a small fleet of services
- Heroku/Render/Fly.io mean vendor dependency and recurring costs
- We wanted: push to main → live in production, automatically, with zero human intervention
How It Works
µPaaS is a single Go binary that:
- Receives Gitea webhooks on push/merge events
- Clones the repo using deploy keys (read-only SSH keys per-repo)
- Runs
docker buildto build the new image - Swaps the running container with the new image
- Routes traffic via Traefik reverse proxy with automatic TLS
The deploy flow:
Developer merges PR to main/prod
→ Gitea fires webhook to µPaaS
→ µPaaS clones repo, builds Docker image
→ µPaaS stops old container, starts new one
→ Traefik routes traffic to new container
→ Site is live on its production URL with TLS
Time from merge to live: typically under 2 minutes (dominated by Docker build time).
Deploy Keys
Each repo that deploys via µPaaS has a read-only SSH deploy key. This means:
- µPaaS can clone the repo to build it, but can't push to it
- Each key is scoped to one repo — compromise of one key doesn't affect others
- No shared credentials, no broad API tokens
What's Deployed This Way
Any Docker-based service or site:
- Static sites (Jekyll, Hugo) — Dockerfile builds the site, nginx serves it
- Go services — Dockerfile builds the binary, runs it
- Web applications — same pattern
Everything gets a production URL with automatic TLS via Traefik.
The Full Pipeline
Putting it all together, the development lifecycle looks like this:
1. Issue filed in Gitea (by human or agent)
↓
2. Agent picks up the issue (via notification poller)
↓
3. Agent posts "starting work on #N" to Mattermost #git
↓
4. Agent (or sub-agent) creates branch, writes code, pushes
↓
5. Gitea webhook fires → #git shows the push
↓
6. CI runs docker build → passes or fails
↓
7. Agent creates PR "(closes #N)"
↓
8. Gitea webhook fires → #git shows the PR
↓
9. Agent reviews code, runs make check locally, verifies
↓
10. Agent assigns PR to human when all checks pass
↓
11. Human reviews, requests changes or approves
↓
12. If changes requested → agent reworks, back to step 6
↓
13. Human merges PR
↓
14. Gitea webhook fires → µPaaS deploys to production
↓
15. Gitea webhook fires → #git shows the merge
↓
16. Site/service is live on production URL
Steps 2-10 can happen without any human involvement. The human's role is reduced to: review the PR, approve or request changes, merge. Everything else is automated.
Observability
Because everything flows through Mattermost channels:
- The human can glance at #git to see the current state of all projects
- CI failures are immediately visible (Gitea Actions posts status)
- Deployments are immediately visible (µPaaS can log to the same channel)
- The agent's work narration in #claw shows what it's currently doing
- No need to check multiple dashboards — one chat client shows everything
Identity Separation
A key architectural decision: the agent has its own identity everywhere.
| System | Human Account | Agent Account |
|---|---|---|
| Gitea | @sneak | @clawbot |
| Mattermost | @sneak | @claw |
This separation means:
- Clear attribution. Every commit, comment, and message shows who did it. When reviewing git history, you know which commits were human and which were agent.
- Independent permissions. The agent can have write access to repos where it's trusted, fork-and-PR access where it's not. The human can have admin access without the agent inheriting it.
- Mentionability. The human can @-mention the agent in an issue comment to assign work. The agent can @-mention the human when it needs review. This works exactly like human-to-human collaboration.
- Separate notification streams. The agent's notification poller watches
@clawbot's inbox. The human's notifications are separate. No cross-talk.
Why Self-Host Everything
The stack — Gitea, Mattermost, µPaaS, OpenClaw — is entirely self-hosted. This isn't ideological; it's practical:
- No API rate limits. The agent makes dozens of API calls per hour to Gitea. GitHub's API limits would throttle it.
- No surprise costs. CI minutes, seat licenses, storage — all free when self-hosted.
- Full API access. Every feature of every tool is available via API. No "enterprise only" gates.
- Custom webhooks. We can wire up any event to any action. Gitea push → Mattermost notification → µPaaS deploy → agent notification, all custom.
- Data sovereignty. Code, issues, conversations, and deployment infrastructure all live on machines we control.
- Offline resilience. If GitHub/Slack/Vercel have an outage, our pipeline keeps running.
The trade-off is maintenance burden, but with an AI agent handling most of the operational work (monitoring, updates, issue triage), the maintenance cost is surprisingly low.
This document describes a production system that's been running since early 2026. The specific tools (Gitea, Mattermost, µPaaS) are interchangeable — the patterns (webhook-driven deployment, real-time activity feeds, identity separation, automated CI gates) apply to any self-hosted stack.