Contents

Self-Hosting Gitea as a GitHub Alternative: Setup, CI/CD, and Mirroring

Gitea is the lightest full-featured Git hosting platform you can self-host. The current stable release, version 1.25, runs comfortably in under 200 MB of RAM as a single Go binary or Docker container. It covers pull requests with code review, issue tracking, CI/CD through GitHub Actions-compatible runners, package registries for containers and NPM, and bidirectional repository mirroring with GitHub. If you want ownership of your source code without the resource overhead of GitLab, Gitea is the strongest option available right now.

Why Gitea Over Forgejo, GitLab, or Gogs

Several self-hosted Git platforms compete for attention, and the right pick depends on your priorities.

Gitea vs. Forgejo. Forgejo forked from Gitea in late 2022 after a for-profit company took ownership of the Gitea project. As of 2026, both are actively developed with nearly identical core features. Forgejo operates under Codeberg e.V., a non-profit, and prioritizes community governance and ActivityPub federation. Forgejo actually has more contributors (232 vs 153) and higher commit velocity in recent months. If federation or governance philosophy matters to you, Forgejo is the better pick. If you want a larger plugin ecosystem and faster feature adoption from upstream, Gitea still has the edge.

Gitea vs. GitLab CE. GitLab Community Edition requires 4 GB of RAM minimum (8 GB recommended) and pulls in PostgreSQL, Redis, Sidekiq, Puma, and a fleet of background services. Gitea runs in 200-512 MB of RAM with a single binary and one database. For a homelab or a team under 50 users, GitLab’s resource overhead is hard to justify.

Gitea vs. Gogs. Gogs is Gitea’s ancestor - Gitea forked from Gogs in 2016. Gogs lacks pull request reviews, CI/CD, and package registries. Development has slowed significantly. There is no compelling reason to choose Gogs for a new installation in 2026.

FeatureGiteaForgejoGitLab CEGogs
Minimum RAM~200 MB~200 MB4 GB+~100 MB
CI/CDGitea ActionsForgejo ActionsBuilt-inNone
Package RegistryYesYesYesNo
FederationExperimentalActive (ActivityPub)NoNo
Migration ToolGitHub, GitLab, BitbucketGitHub, GitLab, BitbucketGitHub, BitbucketLimited
GovernanceFor-profit companyNon-profit (Codeberg)For-profitSingle maintainer

Resource requirements are modest. Gitea with PostgreSQL and an Actions runner runs comfortably on a Raspberry Pi 4 (4 GB) or a $5/month VPS. It handles repositories with 100K+ commits and serves 20+ concurrent users on 2 CPU cores and 1 GB of RAM.

Gitea repository dashboard showing code browser, branch selector, and recent commits
Gitea's web interface for repository browsing
Image: Gitea

Gitea’s feature coverage against GitHub is broad: issues, pull requests with code review, branch protection rules, webhooks, OAuth2 login, organization and team management, container and NPM package registries, project boards (Kanban), wiki, and Actions-compatible CI/CD. The built-in migration tool (Settings > Migrations) imports repositories including issues, pull requests, labels, milestones, and releases from GitHub, GitLab, Bitbucket, and other Gitea instances.

Installation with Docker Compose and PostgreSQL

While Gitea supports SQLite for small setups, PostgreSQL provides the reliability needed for anything beyond experimentation. Here is a production-ready Docker Compose configuration:

services:
  gitea:
    image: gitea/gitea:1.25
    container_name: gitea
    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}
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    mem_limit: 512m
    cpus: 1.0

  db:
    image: postgres:16-alpine
    container_name: gitea-db
    environment:
      - POSTGRES_DB=gitea
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "gitea"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    mem_limit: 256m

volumes:
  gitea_data:
  postgres_data:

Create an .env file alongside this compose file with DB_PASSWORD=your_secure_password_here. The GITEA__section__key format allows overriding any app.ini setting through environment variables.

After running docker compose up -d, navigate to http://your-server:3000 to complete the installation wizard. Set the site title, create your admin account, and configure the server domain and SSH port. These settings land in /data/gitea/conf/app.ini inside the container.

A few app.ini settings to adjust after installation:

[server]
ROOT_URL = https://gitea.yourdomain.com/
SSH_DOMAIN = gitea.yourdomain.com
SSH_PORT = 22

[service]
DISABLE_REGISTRATION = true

Set ROOT_URL to match your reverse proxy URL exactly. Set SSH_PORT to the public-facing port (not the container internal port). Disable public registration after creating your accounts.

For backups, run gitea dump -c /data/gitea/conf/app.ini inside the container to create a ZIP archive of all repositories, the database, and configuration. Schedule this with cron. For faster database restores, also run pg_dump separately against the PostgreSQL container.

SSH Access, Git Operations, and LFS

Git over SSH is the preferred protocol for push and pull operations - no password prompts, faster than HTTPS for large repositories.

SSH passthrough maps the container’s SSH port to the host. With the compose file above, users clone with:

git clone ssh://git@gitea.yourdomain.com:2222/user/repo.git

Add this to ~/.ssh/config for cleaner URLs:

Host gitea.yourdomain.com
    Port 2222
    User git

Users add their public keys in the Gitea web UI under Settings > SSH/GPG Keys. Gitea supports ED25519 (recommended), RSA (4096-bit minimum), and ECDSA key types. For CI/CD systems, use deploy keys - read-only SSH keys assigned to specific repositories, created under repository Settings > Deploy Keys.

Git LFS (Large File Storage) handles binary files that do not belong in regular Git history - images, models, datasets. Enable it in app.ini:

[lfs]
PATH = /data/lfs
MAX_FILE_SIZE = 1073741824

Gitea supports local filesystem, MinIO/S3-compatible object storage, and Azure Blob Storage as LFS backends. Each LFS object is stored once regardless of how many branches reference it.

For environments where SSH is blocked, HTTPS cloning works with git clone https://gitea.yourdomain.com/user/repo.git. Users authenticate with their Gitea password or a personal access token.

Gitea Actions: CI/CD with GitHub Actions Compatibility

Gitea Actions , production-ready since version 1.22, runs GitHub Actions-compatible workflow YAML files using the act_runner agent.

Enable Actions by adding this to app.ini and restarting Gitea:

[actions]
ENABLED = true

This adds an Actions tab to every repository. Without a registered runner, workflows queue but never execute.

Install act_runner by downloading the binary from the Gitea releases page or pulling the Docker image:

docker pull gitea/act_runner:latest

# Register the runner
act_runner register --no-interactive \
  --instance https://gitea.yourdomain.com \
  --token YOUR_RUNNER_TOKEN \
  --name my-runner \
  --labels ubuntu-latest,self-hosted,linux

Get the registration token from Gitea’s admin panel under Site Administration > Runners. The runner uses Docker containers (default: catthehacker/ubuntu:act-latest) to provide isolated build environments.

Gitea Actions interface showing CI/CD workflow runs and status indicators
Gitea Actions CI/CD dashboard
Image: Gitea

Workflow compatibility covers roughly 90% of GitHub Actions syntax: on: push/pull_request triggers, jobs with steps, uses: for third-party actions, environment variables, secrets, matrix builds, and artifacts. Notable gaps in version 1.25 include limited workflow_dispatch inputs UI and restricted caching action support.

Create workflows in .gitea/workflows/ (not .github/). Here is a basic CI pipeline for a Go project:

name: CI
on: [push, pull_request]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go build ./...
      - run: go test ./...

Add repository secrets in Settings > Actions > Secrets and reference them with ${{ secrets.DEPLOY_KEY }}. Organization-wide secrets are available at the organization settings level.

Repository Mirroring and GitHub Backup

Self-hosting Gitea does not mean leaving GitHub behind. Mirror syncing lets you maintain both.

To push from Gitea to GitHub, go to repository Settings > Mirror Settings and add a push mirror with the GitHub remote URL and a personal access token. Gitea pushes all branches and tags on every push, with a configurable sync interval from 1 minute to 24 hours.

To pull from GitHub into Gitea, create a new repository via New Migration, select GitHub, and enable the Mirror option. Gitea pulls changes on a configurable interval (default 8 hours). This is the simplest way to maintain a local backup of important GitHub repositories.

In practice, you use Gitea as your primary development platform and push-mirror to GitHub for visibility. Your GitHub profile stays active, collaborators can find your code, and you retain full control over your primary Git host.

For bulk migration, combine the Gitea API with the gh CLI:

# List all your GitHub repos
gh repo list --json nameWithOwner --limit 500 -q '.[].nameWithOwner'

# Migrate each one via Gitea API
curl -X POST "https://gitea.yourdomain.com/api/v1/repos/migrate" \
  -H "Authorization: token YOUR_GITEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clone_addr": "https://github.com/user/repo.git",
    "mirror": true,
    "repo_name": "repo",
    "repo_owner": "your-gitea-user",
    "service": "github",
    "auth_token": "YOUR_GITHUB_TOKEN"
  }'

Gitea webhooks send GitHub-compatible payloads on push, pull request, and issue events, so many GitHub webhook receivers for Discord, Slack, Matrix, or ntfy work without modification.

Looking ahead, both Gitea and Forgejo are developing ForgeFed/ActivityPub support for cross-instance collaboration - pull requests between different instances without mirroring. As of early 2026, this remains experimental but is progressing steadily.