Self-Hosting Git with Gitea
GitHub is great. But GitHub goes down. And I don’t like the idea of my entire infrastructure definition living on someone else’s computer.
Gitea is my answer. Lightweight, fast, and completely under my control.
Why Self-Host Git?
- Availability - My infrastructure repo is accessible even when the internet isn’t
- Privacy - Sensitive configs never leave my network
- Speed - Local clone/push is instant
- Integration - Direct webhooks to local CI/CD
The Setup
Gitea runs as a Docker container on my main server:
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
ports:
- "3000:3000"
- "222:22"
restart: unless-stopped
Lightweight. Uses SQLite by default (upgrade to PostgreSQL if you have many users).
The Monorepo
I keep infrastructure in a single repository:
infrastructure/
├── ansible/ # Configuration management
├── terraform/ # Cloud resources
├── kubernetes/ # K3s manifests
└── scripts/ # Build and deployment tools
Atomic commits across layers. Change the Ansible role AND the Kubernetes manifest in one commit.
CI/CD with Woodpecker
Gitea integrates with Woodpecker CI (a Drone fork) via OAuth. Push code → webhook fires → pipeline runs.
Pipeline Configuration
# .woodpecker.yml
pipeline:
# Test Python code
test:
image: python:3.11
when:
path:
include: [ 'scripts/**' ]
commands:
- pip install -r requirements.txt
- pytest tests/
# Lint Kubernetes manifests
lint-k8s:
image: stackrox/kube-linter:latest
when:
path:
include: [ 'kubernetes/**' ]
commands:
- kube-linter lint kubernetes/
# Notify on failure
notify:
image: plugins/slack
settings:
webhook:
from_secret: slack_webhook
when:
status: [ failure ]
Path-based triggers mean only relevant tests run. Change a Python script? Run Python tests. Change K8s manifests? Run the linter.
The Workflow
- Push code to Gitea
- Woodpecker detects the webhook
- Spins up containers for each pipeline step
- If it’s Kubernetes changes, Flux picks them up automatically
- Notifications on success/failure
All local. If the internet dies, I can still push, build, and deploy to my network.
Gitea vs GitHub
| Feature | GitHub | Gitea |
|---|---|---|
| Availability | Their uptime | My uptime |
| Privacy | They see everything | Stays local |
| Speed | Internet latency | LAN speed |
| Cost | Free (with limits) | Free (self-hosted) |
| CI/CD | Actions | Woodpecker/Drone |
| Features | Everything | Core Git features |
GitHub still has advantages: collaboration, visibility, Actions ecosystem. I mirror public projects there. But infrastructure code lives on Gitea.
Backup Strategy
Gitea data is just files:
# Daily backup
tar -czf gitea-backup-$(date +%Y%m%d).tar.gz /opt/gitea/data
# Sync to NAS
rclone sync /backups/gitea nas:/backups/gitea
The repos themselves are Git — clone them anywhere as additional backups.
Tips
SSH Keys: Configure Gitea to use a non-standard port (222) to avoid conflicts with system SSH.
Reverse Proxy: Put it behind Nginx/Traefik with SSL. Don’t expose Gitea directly.
Mirrors: Set up push mirrors to GitHub for public repos. Best of both worlds.
Your code, your server, your rules.