Self-Host Gitea: Lightweight GitHub Alternative 2026
TL;DR
Gitea (MIT, ~43K GitHub stars, Go) is a lightweight self-hosted Git service — the most popular GitHub alternative for self-hosters. Runs in a single Go binary or Docker container under 100MB RAM. Full Git hosting: repos, branches, pull requests, issues, wikis, releases, webhooks, CI/CD via Gitea Actions (GitHub Actions-compatible YAML), and organizations. GitHub charges $4/user/month for Teams. Gitea is free on your own hardware.
Key Takeaways
- Gitea: MIT, ~43K stars, Go — full GitHub alternative in <100MB RAM
- Gitea Actions: GitHub Actions-compatible YAML — reuse your existing workflows
- SSH + HTTP: Standard Git push/pull over both SSH and HTTPS
- Migration: Import repos from GitHub, GitLab, Bitbucket with one click
- Packages: Container registry, npm, PyPI, Maven registries built-in
- Federation: Gitea supports basic ActivityPub federation (experimental)
Gitea vs Forgejo vs GitLab CE
| Feature | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| License | MIT | GPL 3.0 | MIT (CE) |
| GitHub Stars | ~43K | ~11K | ~23K |
| RAM usage | ~100MB | ~100MB | ~4GB+ |
| CI/CD | Gitea Actions | Forgejo Actions | GitLab CI |
| Actions compatibility | GitHub Actions YAML | GitHub Actions YAML | Custom format |
| Container registry | Yes | Yes | Yes |
| Package registries | Yes (npm/PyPI/Maven/etc.) | Yes | Yes |
| Federation | Experimental | Yes (ActivityPub) | No |
| Setup complexity | Low | Low | High |
Part 1: Docker Setup
# docker-compose.yml
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
ports:
- "3000:3000"
- "2222:22" # SSH — map host port 2222 to Gitea SSH
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
USER_UID: 1000
USER_GID: 1000
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: postgres:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: "${POSTGRES_PASSWORD}"
GITEA__server__DOMAIN: "git.yourdomain.com"
GITEA__server__ROOT_URL: "https://git.yourdomain.com"
GITEA__server__SSH_DOMAIN: "git.yourdomain.com"
GITEA__server__SSH_PORT: 2222
GITEA__server__HTTP_PORT: 3000
GITEA__service__DISABLE_REGISTRATION: "true" # Disable after setup
GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: gitea
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 10s
start_period: 20s
volumes:
gitea_data:
postgres_data:
# .env
POSTGRES_PASSWORD=your-secure-db-password
docker compose up -d
Visit http://your-server:3000 → complete the installation wizard.
Part 2: HTTPS with Caddy
git.yourdomain.com {
reverse_proxy localhost:3000
}
SSH access
Users push over SSH using port 2222 (or any port you mapped):
# Clone via SSH:
git clone ssh://git@git.yourdomain.com:2222/username/repo.git
# Or add to ~/.ssh/config for convenience:
# Host git.yourdomain.com
# Port 2222
Part 3: First-Time Setup
- Visit
https://git.yourdomain.com - Complete the installation wizard (DB settings are pre-populated from env vars)
- Create the admin account
- After setup, disable registration:
environment:
GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__service__ALLOW_ONLY_EXTERNAL_SELF_REGISTRATION: "false"
Or via app.ini:
[service]
DISABLE_REGISTRATION = true
Part 4: SSH Key Setup
# Generate SSH key (if you don't have one):
ssh-keygen -t ed25519 -C "your@email.com"
# Copy public key:
cat ~/.ssh/id_ed25519.pub
# In Gitea: User Settings → SSH / GPG Keys → Add Key
# Paste public key content
# Test connection:
ssh -T git@git.yourdomain.com -p 2222
# Returns: Hi username! You've successfully authenticated.
# Clone:
git clone ssh://git@git.yourdomain.com:2222/myorg/myrepo.git
# Or set remote:
git remote add origin ssh://git@git.yourdomain.com:2222/myorg/myrepo.git
Part 5: Gitea Actions (CI/CD)
Gitea Actions uses GitHub Actions-compatible YAML. Enable it:
environment:
GITEA__actions__ENABLED: "true"
Run an Act Runner
# Add to docker-compose.yml:
services:
gitea-runner:
image: gitea/act_runner:latest
container_name: gitea_runner
restart: unless-stopped
volumes:
- ./runner-config.yaml:/config.yaml
- gitea_runner_data:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "https://git.yourdomain.com"
GITEA_RUNNER_REGISTRATION_TOKEN: "${RUNNER_TOKEN}"
GITEA_RUNNER_NAME: "docker-runner"
GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye"
# Get runner token from Gitea:
# Site Administration → Runners → Create new runner → copy token
# Register:
docker exec gitea_runner gitea-act-runner register \
--instance https://git.yourdomain.com \
--token YOUR_RUNNER_TOKEN \
--name docker-runner \
--labels ubuntu-latest:docker://node:20-bullseye
Example GitHub Actions-compatible workflow
# .gitea/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run build
docker:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
docker build -t git.yourdomain.com/${{ gitea.repository }}:${{ gitea.sha }} .
docker push git.yourdomain.com/${{ gitea.repository }}:${{ gitea.sha }}
Part 6: Container Registry
Gitea includes a container registry at git.yourdomain.com:
# Login:
docker login git.yourdomain.com -u username -p your-password
# Push:
docker tag myimage:latest git.yourdomain.com/username/myrepo:latest
docker push git.yourdomain.com/username/myrepo:latest
# Pull:
docker pull git.yourdomain.com/username/myrepo:latest
Part 7: Migrate from GitHub
Single repository
# Gitea UI: + → New Migration → GitHub
# Enter: GitHub repo URL + access token (for private repos)
# Gitea mirrors: issues, PRs, releases, labels, milestones
Bulk migration script
#!/bin/bash
# Migrate all repos from a GitHub org to Gitea
GITHUB_TOKEN="ghp_yourtoken"
GITHUB_ORG="your-github-org"
GITEA_URL="https://git.yourdomain.com"
GITEA_TOKEN="your-gitea-token"
GITEA_ORG="your-gitea-org"
# List all repos:
REPOS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/orgs/${GITHUB_ORG}/repos?per_page=100" \
| jq -r '.[].full_name')
for REPO in $REPOS; do
echo "Migrating $REPO..."
curl -s -X POST "${GITEA_URL}/api/v1/repos/migrate" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"clone_addr\": \"https://github.com/${REPO}\",
\"auth_token\": \"${GITHUB_TOKEN}\",
\"uid\": 1,
\"repo_name\": \"$(echo $REPO | cut -d'/' -f2)\",
\"mirror\": false,
\"issues\": true,
\"pull_requests\": true,
\"releases\": true,
\"labels\": true,
\"milestones\": true
}"
done
Part 8: Organization and Team Access
Organization: mycompany
├── Team: Owners (full access)
│ └── Users: alice, bob
├── Team: Developers (write access)
│ ├── Users: charlie, diana
│ └── Repos: backend, frontend, infra
└── Team: Contractors (read access)
├── Users: external1
└── Repos: frontend (read only)
Create via API:
# Create org:
curl -X POST "${GITEA_URL}/api/v1/orgs" \
-H "Authorization: token ${TOKEN}" \
-d '{"username": "mycompany", "visibility": "private"}'
# Create team:
curl -X POST "${GITEA_URL}/api/v1/orgs/mycompany/teams" \
-H "Authorization: token ${TOKEN}" \
-d '{"name": "Developers", "permission": "write", "units": ["repo.code", "repo.issues"]}'
Maintenance
# Update Gitea:
docker compose pull
docker compose up -d
# Backup:
# Stop Gitea first for consistent backup:
docker compose stop gitea
tar -czf gitea-backup-$(date +%Y%m%d).tar.gz \
$(docker volume inspect gitea_gitea_data --format '{{.Mountpoint}}')
docker exec gitea-postgres-1 pg_dump -U gitea gitea \
| gzip > gitea-db-$(date +%Y%m%d).sql.gz
docker compose start gitea
# Logs:
docker compose logs -f gitea
# Admin CLI:
docker exec -u git gitea gitea admin user create \
--username alice --email alice@company.com --password secret --admin
# Gitea version:
docker exec gitea gitea --version
See all open source DevOps and Git tools at OSSAlt.com/categories/devops.