<!-- OSSAlt AI-readable guide source -->
<!-- Canonical: https://ossalt.com/guides/gitea-vs-github-self-hosted-git-2026 -->
<!-- Raw Markdown: https://ossalt.com/guides/gitea-vs-github-self-hosted-git-2026/raw.md -->
<!-- Source path: content/guides/gitea-vs-github-self-hosted-git-2026.mdx -->

---
og_image: "/images/guides/gitea-vs-github-self-hosted-git-2026.webp"
title: "Gitea vs GitHub: Self-Hosted Git 2026"
description: "Gitea vs GitHub self-hosted in 2026: resource usage, CI/CD, and migration steps compared. Gitea runs on 256MB RAM. Full feature matrix and setup guide included."
date: "2026-03-29"
author: "OSSAlt Team"
tags: ["gitea", "github", "self-hosted", "git", "ci-cd", "docker", "migration", "2026"]
---

# Gitea vs GitHub: Self-Hosted Git 2026

## TL;DR

Gitea is a lightweight self-hosted Git forge written in Go that runs on 256MB RAM and deploys in under 15 minutes with Docker Compose. GitHub is a SaaS platform with the largest developer community in the world, unlimited public repos for free, and deeply integrated CI/CD through GitHub Actions. The decision is not about which tool is technically better — it's about what you're optimizing for. Teams that need code to stay on-premises (compliance, air-gapped networks, sovereignty), want to cut GitHub Team/Enterprise costs at scale, or simply want ownership of their development infrastructure should look seriously at Gitea. Teams building open source projects, needing the GitHub ecosystem (Actions marketplace, Packages, Copilot, Dependabot), or without capacity to manage infrastructure should stay on GitHub.

## Key Takeaways

- **Resource footprint**: Gitea idles at 100–256MB RAM on a $6/month VPS — GitHub SaaS has no server to manage but costs $4/user/month (Team) or $21/user/month (Enterprise)
- **Gitea Actions**: Gitea 1.19+ ships GitHub Actions-compatible YAML workflows — many GitHub Actions workflows run on Gitea with minimal changes
- **Migration**: Gitea's built-in migration tool imports repos, issues, pull requests, milestones, labels, and releases directly from GitHub via API
- **CI/CD gap**: GitHub Actions has 20,000+ community actions; Gitea Actions runs the same YAML but has a smaller ecosystem — `act_runner` is the execution engine
- **Feature parity**: Gitea covers the 80% of GitHub features most teams use — missing GitHub Copilot, Codespaces, advanced security (GHAS), Projects v2 advanced features, and the community discovery layer
- **Total cost at scale**: 20 developers on GitHub Team costs $960/year; a Hetzner CX22 running Gitea costs ~$90/year

---

## Why Teams Are Leaving GitHub in 2026

GitHub's acquisition by Microsoft in 2018 went smoothly for most teams. The service improved significantly: GitHub Actions replaced third-party CI, Packages added container and package registries, Copilot added AI code completion, and the free tier became genuinely competitive.

But several pressures are pushing teams toward self-hosted alternatives in 2026:

**Cost at scale**: GitHub Free is competitive for open source. GitHub Team at $4/seat/month adds nothing for small teams that don't need advanced code review or team permissions. GitHub Enterprise at $21/seat/month is significant — a 100-person engineering org pays $25,200/year. At that scale, a self-hosted Gitea instance with 4GB RAM on Hetzner runs at ~$300/year, a 98% cost reduction on infrastructure alone.

**Data residency requirements**: Financial services, healthcare, and government contractors often face regulatory requirements to keep source code within specific geographic boundaries or on-premises. GitHub offers GitHub Enterprise Server (a self-hosted version of GitHub), but it starts at the Enterprise license tier. Gitea achieves equivalent data residency goals at a fraction of the cost.

**AI training concerns**: GitHub's terms of service allow Microsoft/GitHub to use code for AI model training (opt-out possible but not always practical for organizations). Self-hosted Gitea eliminates this concern entirely.

**Vendor lock-in**: GitHub's ecosystem lock-in has deepened — Actions workflows, Packages, GitHub-specific CODEOWNERS, Actions secrets, and Environments all tie projects to the platform. Teams prioritizing portability prefer forges that support open standards.

---

## Resource Footprint Comparison

### Gitea on a $6/month VPS

Gitea is written in Go — it compiles to a single binary, starts in under a second, and has extremely low memory overhead.

| Team Size | RAM | CPU | Monthly VPS Cost |
|-----------|-----|-----|-----------------|
| Solo / 1–5 devs | 256MB–512MB | 1 vCPU | $4–6 (Hetzner CAX11) |
| 5–20 devs | 512MB–1GB | 2 vCPU | $6–12 (Hetzner CX22) |
| 20–100 devs | 1–2GB | 2 vCPU | $12–20 (Hetzner CX32) |
| 100–500 devs | 2–4GB | 4 vCPU | $20–40 (Hetzner CX42) |

These numbers include Gitea + PostgreSQL. If you run Gitea Actions runners on the same machine, add ~500MB–1GB per concurrent runner.

Gitea's Go binary idle memory is under 100MB. The memory growth is driven primarily by Git operations (large repos, many concurrent clones) and the database.

### GitHub SaaS Pricing

| Plan | Cost | Notes |
|------|------|-------|
| Free | $0 | 500MB package storage, 2,000 Actions minutes/month |
| Team | $4/user/month | Unlimited private repos, 50GB storage, 3,000 Actions minutes |
| Enterprise | $21/user/month | Compliance, SAML SSO, audit log streaming, GHAS |
| Enterprise Server | Custom + $21/user/month | Self-hosted GitHub — at Gitea's target users, this is overkill |

For a team of 10 on GitHub Team: $480/year. The equivalent Gitea setup on a Hetzner CX22: $86/year. The ROI comparison becomes clear at 20+ seats.

---

## Feature Comparison: Gitea vs GitHub

### Core Git Hosting

| Feature | Gitea | GitHub |
|---------|-------|--------|
| Private/public repos | ✅ | ✅ |
| Repo size limit | Configurable (no hard limit) | 5GB soft limit (practical) |
| LFS support | ✅ | ✅ (1GB free, then billed) |
| Protected branches | ✅ | ✅ |
| Branch rules | Basic | Advanced (CODEOWNERS, required status checks, bypass lists) |
| Pull requests | ✅ | ✅ |
| Code review / inline comments | ✅ | ✅ |
| Draft PRs | ✅ | ✅ (Team+) |
| PR templates | ✅ | ✅ |
| Merge strategies | Merge, rebase, squash | Merge, rebase, squash |
| Auto-delete branch on merge | ✅ | ✅ |
| Signed commits verification | ✅ | ✅ |
| Commit signature display | ✅ | ✅ |
| Dependency graph | ❌ | ✅ |
| Vulnerability alerts | ❌ | ✅ (Dependabot) |

### Issue Tracking

| Feature | Gitea | GitHub |
|---------|-------|--------|
| Issues | ✅ | ✅ |
| Labels | ✅ | ✅ |
| Milestones | ✅ | ✅ |
| Projects / Kanban | Basic (Kanban board) | ✅ (Projects v2 — advanced tables, roadmaps) |
| Issue templates | ✅ | ✅ |
| Issue forms (YAML) | ✅ | ✅ |
| Cross-repo references | Within same instance | ✅ (across all GitHub) |
| Issue search | Basic | ✅ (advanced filters) |
| Saved issue searches | ❌ | ✅ |

### CI/CD

| Feature | Gitea Actions | GitHub Actions |
|---------|--------------|----------------|
| YAML syntax | GitHub-compatible | Native |
| `act_runner` | ✅ (self-hosted only) | N/A (managed + self-hosted) |
| Managed runners (cloud) | ❌ | ✅ (Linux, macOS, Windows) |
| Matrix builds | ✅ | ✅ |
| Reusable workflows | ✅ | ✅ |
| Marketplace actions | ~500+ compatible | 20,000+ (official marketplace) |
| Workflow caching | ✅ (`actions/cache`) | ✅ |
| Environments + secrets | ✅ | ✅ |
| OIDC tokens | ✅ | ✅ |
| Artifact storage | Local (configurable) | GitHub-managed (500MB–50GB) |
| Status checks on PRs | ✅ | ✅ |

### Ecosystem and Integrations

| Feature | Gitea | GitHub |
|---------|-------|--------|
| Package registry (npm, PyPI, etc.) | ✅ | ✅ |
| Container registry | ✅ | ✅ (GHCR) |
| Webhooks | ✅ | ✅ |
| REST API | ✅ | ✅ |
| GraphQL API | ❌ | ✅ |
| Third-party integrations | Limited | Vast (Slack, Jira, Linear, etc.) |
| AI code assistant | ❌ | ✅ (Copilot) |
| Codespaces / Cloud IDE | ❌ | ✅ |
| Advanced Security (GHAS) | ❌ | ✅ (Enterprise) |
| Code scanning / SAST | ❌ | ✅ (CodeQL) |
| Secret scanning | ❌ | ✅ |
| Audit log streaming | ✅ | ✅ (Enterprise) |
| SAML SSO | ✅ | ✅ (Enterprise) |
| LDAP | ✅ | ❌ |
| OAuth providers | ✅ (GitHub, Google, etc.) | Limited |
| ActivityPub/federation | ❌ | ❌ |
| Gitea-to-Gitea migration | ✅ | N/A |
| Mirror from GitHub | ✅ | N/A |

---

## Gitea Actions: Running GitHub Workflows on Gitea

Gitea 1.19 (released March 2023) shipped Gitea Actions, which uses the same YAML workflow syntax as GitHub Actions. If your team is moving from GitHub to Gitea, most workflows will run unchanged or with minimal edits.

### What's Compatible

```yaml
# This GitHub Actions workflow runs on Gitea Actions with no changes
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest  # Maps to a Gitea act_runner with ubuntu label
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      - run: npm ci
      - run: npm test
      - uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results/
```

### What Requires Changes

**1. `runs-on` labels**: GitHub's managed runners use `ubuntu-latest`, `macos-latest`, `windows-latest`. Gitea uses whatever labels you assign to `act_runner` instances. You must provision your own runners and assign matching labels.

```yaml
# If your Gitea runner is labeled 'ubuntu-22.04':
runs-on: ubuntu-22.04  # instead of ubuntu-latest
```

**2. GitHub-specific actions**: Some marketplace actions directly interact with GitHub APIs:

```yaml
# These GitHub-specific actions don't work on Gitea:
- uses: actions/github-script@v7  # Requires GitHub API
- uses: github/codeql-action@v3   # GitHub-only
- uses: actions/stale@v9          # GitHub Issues API

# These work fine on Gitea:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/setup-python@v5
- uses: docker/build-push-action@v5
- uses: actions/upload-artifact@v4
- uses: actions/cache@v4
```

**3. `GITHUB_TOKEN` vs `GITEA_TOKEN`**: Workflows that use `${{ secrets.GITHUB_TOKEN }}` for authenticated API calls need to reference `${{ secrets.GITEA_TOKEN }}` or the built-in Gitea token:

```yaml
# GitHub:
token: ${{ secrets.GITHUB_TOKEN }}

# Gitea:
token: ${{ secrets.GITEA_TOKEN }}
# or use the built-in:
token: ${{ gitea.token }}
```

### Setting Up `act_runner`

`act_runner` is the Gitea-maintained daemon that executes workflow jobs. Each `act_runner` instance registers with your Gitea instance and picks up jobs via polling.

```yaml
# docker-compose.yml — add act_runner to your Gitea stack
  act_runner:
    image: gitea/act_runner:latest
    restart: always
    environment:
      GITEA_INSTANCE_URL: "https://git.yourdomain.com"
      GITEA_RUNNER_REGISTRATION_TOKEN: "${RUNNER_REGISTRATION_TOKEN}"
      GITEA_RUNNER_NAME: "primary-runner"
      GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://ubuntu:22.04"
    volumes:
      - act_runner_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
```

Get the registration token from your Gitea instance: **Site Administration → Actions → Runners → Create new runner**.

For each label in `GITEA_RUNNER_LABELS`, the format is `<label>:<executor>`. Docker executors pull images on demand — the runner needs Docker access. For native execution (running jobs directly on the host), use `<label>:host`.

---

## Docker Compose Setup: Gitea

### Full Production Stack

```yaml
# docker-compose.yml
services:
  gitea:
    image: gitea/gitea:latest
    restart: always
    environment:
      USER_UID: 1000
      USER_GID: 1000
      GITEA__database__DB_TYPE: postgres
      GITEA__database__HOST: db:5432
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea
      GITEA__database__PASSWD: ${DB_PASSWORD}
      GITEA__server__DOMAIN: git.yourdomain.com
      GITEA__server__ROOT_URL: https://git.yourdomain.com/
      GITEA__server__HTTP_PORT: 3000
      GITEA__server__SSH_PORT: 22
      GITEA__server__SSH_LISTEN_PORT: 22
      GITEA__mailer__ENABLED: "true"
      GITEA__mailer__SMTP_ADDR: smtp.resend.com
      GITEA__mailer__SMTP_PORT: "587"
      GITEA__mailer__USER: resend
      GITEA__mailer__PASSWD: ${SMTP_PASSWORD}
      GITEA__mailer__FROM: git@yourdomain.com
      GITEA__service__DISABLE_REGISTRATION: "false"
      GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "22:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_DB: gitea
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  gitea_data:
  postgres_data:
```

```bash
# .env
DB_PASSWORD=generate-a-strong-password-here
SMTP_PASSWORD=your-smtp-api-key
```

### Nginx Reverse Proxy

```nginx
server {
    listen 80;
    server_name git.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name git.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.com/privkey.key;

    client_max_body_size 512m;  # Allow large repo pushes

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}
```

```bash
# Deploy
docker compose up -d

# Get SSL certificate
certbot --nginx -d git.yourdomain.com

# Initial setup: visit https://git.yourdomain.com to complete web installer
# (or configure all settings via environment variables — no web installer runs
# when DB is already configured)
```

### Post-Install Configuration

After the web installer completes (or on first run with environment variables):

1. **Create admin account**: The first registered user becomes admin by default
2. **Configure SSH keys**: Users add SSH keys under their profile settings
3. **Set up organization**: Mirror GitHub's org structure under Administration → Organizations
4. **Enable Gitea Actions**: Administration → Site Administration → Settings → Actions → Enable Gitea Actions

---

## Migrating from GitHub to Gitea

Gitea includes a built-in migration tool that pulls from GitHub via API. It preserves:

- Repository content (all branches, tags, commits)
- Issues (title, body, comments, labels, milestones, state)
- Pull requests (with associated comments and review threads)
- Releases and release assets
- Wiki pages
- Labels and milestones

### Step 1: Generate a GitHub Personal Access Token

Go to GitHub → Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens.

Required permissions:
- `Contents: Read`
- `Issues: Read`
- `Pull requests: Read`
- `Metadata: Read`

For organizations, also:
- `Members: Read`

### Step 2: Run the Migration in Gitea

**Via web UI:**

1. In Gitea, click **+** (new repository) → **Migrate an external repository**
2. Select **GitHub** as the source
3. Enter:
   - GitHub URL: `https://github.com`
   - Personal Access Token: your PAT from Step 1
   - Owner: your GitHub username or org
   - Repository: the repository name
4. Under "Migration Items", check: Issues, Pull Requests, Labels, Milestones, Releases, Wiki
5. Click **Migrate Repository**

**Via API (for bulk migration):**

```bash
#!/bin/bash
# migrate-from-github.sh
# Migrate all repos from a GitHub organization to Gitea

GITEA_URL="https://git.yourdomain.com"
GITEA_TOKEN="your-gitea-admin-token"
GITHUB_TOKEN="your-github-pat"
GITHUB_ORG="your-github-org"
GITEA_ORG="your-gitea-org"

# Get list of GitHub repos
repos=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/orgs/$GITHUB_ORG/repos?per_page=100&type=all" \
  | jq -r '.[].name')

for repo in $repos; do
  echo "Migrating: $repo"
  curl -s -X POST "$GITEA_URL/api/v1/repos/migrate" \
    -H "Authorization: Bearer $GITEA_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{
      \"clone_addr\": \"https://github.com/$GITHUB_ORG/$repo\",
      \"auth_token\": \"$GITHUB_TOKEN\",
      \"repo_name\": \"$repo\",
      \"repo_owner\": \"$GITEA_ORG\",
      \"mirror\": false,
      \"private\": true,
      \"issues\": true,
      \"pull_requests\": true,
      \"releases\": true,
      \"labels\": true,
      \"milestones\": true,
      \"wiki\": true
    }"
  echo ""
  sleep 2  # Rate limit consideration
done
```

### Step 3: Migrate Team Members

GitHub users are identified by their GitHub username during migration — Gitea creates placeholder accounts. You'll need to map GitHub usernames to actual Gitea accounts.

```bash
# List all migrated users (placeholders) via Gitea API
curl -H "Authorization: Bearer $GITEA_TOKEN" \
  "$GITEA_URL/api/v1/admin/users?limit=50" | jq '.[].login'
```

For each team member:
1. Have them register on your Gitea instance
2. Use Admin → Users to find their account
3. Reassign issues and PRs via Gitea admin UI or API

For organizations with LDAP/Active Directory, configure LDAP authentication before migration so users can log in with existing credentials:

**Administration → Authentication Sources → Add Authentication Source → LDAP**

### Step 4: Update CI/CD References

GitHub Actions workflow files (`.github/workflows/*.yml`) need minor updates:

```yaml
# Before (GitHub)
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to production
        uses: appleboy/ssh-action@v1  # Works on Gitea too
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /app && git pull && npm install && pm2 restart app
```

Move workflow files from `.github/workflows/` to `.gitea/workflows/` (Gitea also supports `.github/workflows/` for compatibility).

### Step 5: Set Up Mirroring (Optional Transition Period)

During the transition, you can keep GitHub as a push mirror. New commits to Gitea automatically propagate to GitHub, keeping both in sync until the team is fully migrated.

In Gitea: Repository Settings → **Mirrors** → **Add Push Mirror**
- Remote URL: `https://github.com/your-org/repo.git`
- Interval: Every hour
- Username: your GitHub username
- Password/Token: your GitHub PAT with `Contents: Write` permission

---

## GitHub Features Gitea Cannot Replace

### GitHub Copilot

GitHub Copilot is integrated directly into VS Code, JetBrains, Neovim, and other editors and requires a GitHub account. Gitea has no AI assistant. Teams that move to Gitea can continue using Copilot (it works independently of the remote repository host) — the GitHub integration is for the IDE extension, not the Git server.

Alternative: JetBrains AI Assistant, Cursor, Codeium (free tier available), or self-hosted Ollama with Continue.dev.

### GitHub Codespaces

Cloud development environments that launch a VS Code instance with your repo pre-loaded. No Gitea equivalent. Self-hosted alternatives:

- **Gitpod Community** — open source workspace manager, self-hostable
- **code-server** — VS Code in a Docker container, accessible via browser
- **Devcontainer** — VS Code Remote Containers (local, not cloud)

### Advanced Security (GHAS)

GitHub Advanced Security includes CodeQL (SAST), secret scanning, dependency review, and code scanning. These are GitHub-only features available on Enterprise plans.

Self-hosted alternatives:
- **Semgrep OSS** — static analysis, runs in CI as a workflow step
- **Trivy** — container and dependency vulnerability scanning
- **Gitleaks** — secret detection in Git history, runs as a pre-commit hook or CI step
- **OWASP Dependency-Check** — dependency vulnerability scanner

```yaml
# Add secret scanning to Gitea Actions workflow
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for Gitleaks
      - name: Scan for secrets
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ gitea.token }}
      - name: Dependency vulnerability scan
        run: |
          docker run --rm -v $(pwd):/src \
            aquasec/trivy:latest fs /src \
            --exit-code 1 --severity HIGH,CRITICAL
```

### GitHub Packages + GHCR at Scale

Gitea includes a container registry and package registry (npm, PyPI, Maven, NuGet, RubyGems, Helm, etc.), but GitHub's GHCR has significant CDN infrastructure behind it. For public container images pulled millions of times, GitHub's global CDN is meaningfully faster than a self-hosted registry.

For internal/private images, Gitea's registry performs comparably.

---

## Cost Analysis: Gitea vs GitHub at Scale

### 10-Person Team

| Option | Annual Cost | Notes |
|--------|-------------|-------|
| GitHub Free | $0 | 2,000 Actions minutes/month limit — hits ceiling quickly |
| GitHub Team | $480 | $4 × 10 × 12 |
| Gitea on Hetzner CAX11 | $57 | 2 vCPU, 4GB RAM, ARM64 — comfortable for 10 devs |
| Gitea + act_runner on CX22 | $86 | Separate CI runner instance |

### 50-Person Team

| Option | Annual Cost | Notes |
|--------|-------------|-------|
| GitHub Team | $2,400 | $4 × 50 × 12 |
| GitHub Enterprise | $12,600 | $21 × 50 × 12 |
| Gitea on Hetzner CX32 | $216 | 8 vCPU, 16GB RAM — handles 50+ devs |
| Gitea + 2× act_runners | $432 | Separate dedicated runner machines |

### 100-Person Team

| Option | Annual Cost | Notes |
|--------|-------------|-------|
| GitHub Team | $4,800 | |
| GitHub Enterprise | $25,200 | |
| Gitea cluster (CX42 + dedicated DB) | $800–1,200 | Includes redundancy |
| GitLab CE (if feature parity needed) | $1,200–2,000 | Higher resource requirements |

At 50+ seats, the annual savings from Gitea vs GitHub Team typically exceeds $2,000 — well beyond what annual maintenance costs in engineer time (typically 2–4 hours/month for a stable Gitea instance).

---

## When to Stay on GitHub

The self-hosting savings don't apply to every situation. GitHub remains the right choice when:

**You're building open source**: GitHub is where developers discover projects. Stars, forks, contributor networks, and GitHub Sponsors all depend on being on GitHub. A self-hosted Gitea instance is invisible to the open source community. You can mirror to GitHub, but the primary development experience should stay where your audience is.

**You rely on GitHub Actions ecosystem**: The 20,000+ marketplace actions represent years of community investment. GitHub-specific actions for deploying to AWS, Azure, and Google Cloud, managing GitHub Issues, triggering Dependabot, and integrating with external services often have no direct Gitea equivalent. Evaluating which actions you use before migrating is essential.

**You need Copilot**: GitHub Copilot works on any repository you can access, but the editor integration and context features deepen when the repo is on GitHub. Enterprise Copilot features (code referencing, policy control, org-wide management) require GitHub Enterprise.

**Compliance requires managed infrastructure**: GitHub SOC 2, ISO 27001, FedRAMP Moderate, and HIPAA BAA attestations are mature. Self-hosted Gitea means you own the compliance posture — valid for teams building toward those certifications themselves, but a significant burden for teams without dedicated security staff.

**You don't have infrastructure capacity**: Gitea is low-maintenance by self-hosting standards, but it still requires a server, backups, updates, and someone responsible for uptime. If engineering time costs more than $480/year (10 seats), the economics can favor GitHub Team.

---

## Backup and Disaster Recovery

Self-hosted Gitea means you own your backup strategy.

```bash
#!/bin/bash
# backup-gitea.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/var/backups/gitea"
mkdir -p "$BACKUP_DIR"

# Gitea's built-in dump — archives all repos, config, attachments, and DB
docker exec gitea gitea dump -c /data/gitea/conf/app.ini \
  -f "/tmp/gitea-dump-$DATE.zip" --type zip

# Copy dump out of container
docker cp "gitea:/tmp/gitea-dump-$DATE.zip" "$BACKUP_DIR/"

# PostgreSQL dump (belt-and-suspenders — also captured by Gitea dump)
docker exec gitea-db-1 pg_dump -U gitea gitea \
  > "$BACKUP_DIR/postgres-$DATE.sql"

# Optional: push to S3-compatible storage
aws s3 cp "$BACKUP_DIR/gitea-dump-$DATE.zip" \
  "s3://your-backup-bucket/gitea/"

# Cleanup local backups older than 7 days
find "$BACKUP_DIR" -name "*.zip" -mtime +7 -delete
find "$BACKUP_DIR" -name "*.sql" -mtime +7 -delete

echo "Backup complete: gitea-dump-$DATE.zip"
```

```bash
# Add to cron (daily at 3 AM)
0 3 * * * /opt/gitea/backup-gitea.sh >> /var/log/gitea-backup.log 2>&1
```

### Restore from Backup

```bash
# Stop Gitea
docker compose down gitea

# Extract backup archive
unzip gitea-dump-YYYYMMDD-HHMMSS.zip -d /tmp/gitea-restore/

# Restore repos (git data)
cp -r /tmp/gitea-restore/repos/* /path/to/gitea_data/gitea/repositories/

# Restore PostgreSQL
docker compose up -d db
docker exec -i gitea-db-1 psql -U gitea gitea < /tmp/gitea-restore/gitea-db.sql

# Restart Gitea — it will regenerate hooks and indexes
docker compose up -d gitea
docker exec gitea gitea admin regenerate hooks
docker exec gitea gitea admin regenerate keys
```

---

## Decision Framework

**Choose Gitea self-hosted if:**
- Your team is 10+ people paying GitHub Team/Enterprise and cost reduction is a priority
- Code must remain on-premises or within a specific jurisdiction
- You have an air-gapped or restricted network environment
- LDAP/Active Directory authentication integration is required (Gitea supports it; GitHub SaaS does not)
- You value independence from GitHub's roadmap and platform decisions
- Server administration is manageable — Gitea is the least complex self-hosted Git forge to operate

**Choose GitHub if:**
- You're building or contributing to open source and need community visibility
- Your CI/CD pipelines depend heavily on the Actions marketplace ecosystem
- GitHub Copilot is part of your development workflow
- You need GitHub Advanced Security (CodeQL, secret scanning, Dependabot)
- Managed compliance certifications (SOC 2, FedRAMP) are a hard requirement
- The team lacks bandwidth to manage infrastructure

**Consider a hybrid approach if:**
- You need Gitea for internal/private projects and GitHub for open source contributions
- You want Gitea as primary but mirror public repos to GitHub for discoverability
- You're in a transition period — run both with push mirroring from Gitea to GitHub during cutover

---

## Related Guides

For teams building self-hosted infrastructure beyond Git:

- [Self-Host Plausible vs Google Analytics 2026](/guides/self-host-plausible-vs-google-analytics-2026) — the same data ownership philosophy applied to analytics
- [Self-Host Nextcloud on Docker 2026](/guides/how-to-self-host-nextcloud-cloud-storage-collaboration-2026) — replace Google Drive and cloud file storage with a self-hosted alternative

---

*Related: [Gitea vs Forgejo vs GitLab 2026](/guides/gitea-vs-forgejo-vs-gitlab-2026) · [Self-Host Woodpecker CI 2026](/guides/self-host-woodpecker-ci-2026) · [Complete Self-Hosting Stack 2026](/guides/complete-self-hosting-stack-2026)*
