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?

  1. Availability - My infrastructure repo is accessible even when the internet isn’t
  2. Privacy - Sensitive configs never leave my network
  3. Speed - Local clone/push is instant
  4. 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

  1. Push code to Gitea
  2. Woodpecker detects the webhook
  3. Spins up containers for each pipeline step
  4. If it’s Kubernetes changes, Flux picks them up automatically
  5. Notifications on success/failure

All local. If the internet dies, I can still push, build, and deploy to my network.

Gitea vs GitHub

FeatureGitHubGitea
AvailabilityTheir uptimeMy uptime
PrivacyThey see everythingStays local
SpeedInternet latencyLAN speed
CostFree (with limits)Free (self-hosted)
CI/CDActionsWoodpecker/Drone
FeaturesEverythingCore 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.