Skip to main content

How to Self-Host Gitea: GitHub Alternative on Your Own Server 2026

·OSSAlt Team
giteagithubself-hostingdockergitci-cd2026

TL;DR

Gitea is a lightweight, open source GitHub alternative written in Go. It runs on a $6/month VPS, uses ~150MB RAM for small teams, and provides code hosting, pull requests, issues, wikis, and GitHub Actions-compatible CI/CD (via Gitea Actions). Setup takes about 20 minutes. If you need a full self-hosted GitHub replacement for a private team or open source project with sovereignty requirements, Gitea is the leading choice.

Key Takeaways

  • Gitea: MIT license, ~46K GitHub stars, Go-based, runs on 512MB RAM
  • Gitea Actions: GitHub Actions-compatible CI/CD — your existing workflow files work
  • Resource usage: ~150MB RAM for a team of 10; ~40MB for a personal instance
  • Setup time: ~20 minutes (Docker Compose + web installer)
  • Gitea vs Forgejo: Forgejo is a community fork with more open governance — both are good options
  • Gitea vs GitLab: Gitea is far lighter (~150MB vs GitLab's 4GB+ minimum)

Why Self-Host Your Git Server?

  • Privacy: Code never leaves your infrastructure
  • Cost: No per-user pricing; ~$6/month VPS for unlimited repos and users
  • Control: Custom integrations, your own CI runners, private mirror networks
  • Compliance: Air-gapped deployments for regulated industries
  • Performance: Local network access — pushing large repos is faster

Gitea vs Alternatives

FeatureGiteaForgejoGitLab CEOneDev
LanguageGoGoRuby/GoJava
LicenseMITGPL 3.0MITMIT
GitHub Stars~46K~9K~23K~13K
RAM minimum150MB150MB4GB1GB
GitHub Actions compat.✅ (partial)
Container registry
Kubernetes supportVia HelmVia HelmLimited
Best forSmall teams, lightweightPrivacy-focused OSSEnterpriseIntegrated CI

Gitea vs Forgejo: Gitea's development moved to a company structure (Gitea Limited) in 2022, prompting a community fork called Forgejo. Both remain fully open source and compatible. Use Forgejo if open governance matters to you; use Gitea for the original project.


Server Requirements

  • Minimum: 1 vCPU, 512MB RAM, 10GB storage
  • Recommended: 2 vCPU, 1GB RAM, 20GB storage (Hetzner CX22 at €4.35/month)
  • Database: SQLite works for personal use; Postgres recommended for teams

Part 1: Docker Compose Setup

docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  gitea:
    image: gitea/gitea:latest
    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=${POSTGRES_PASSWORD}
    restart: unless-stopped
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"   # Web UI
      - "2222:22"     # SSH (avoids conflict with host SSH on 22)
    depends_on:
      - db

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

volumes:
  gitea_data:
  postgres_data:
# .env file:
POSTGRES_PASSWORD=your-secure-password-here
docker compose up -d

Gitea is now running at http://your-server:3000.


Part 2: Web Installer

Visit http://your-server:3000 on first run. Gitea shows a setup wizard:

Key settings:

  • Database: Select Postgres, confirm the values you set
  • Site title: Your organization name
  • Repository root path: /data/gitea/repositories (default, inside container)
  • Git user: git
  • SSH server domain: your-server-ip or your domain
  • SSH server port: 2222 (matching your docker-compose port mapping)
  • HTTP root URL: https://git.yourdomain.com (your final domain)
  • Admin account: Create the first admin here

Click Install Gitea and you're set.


Part 3: DNS + Reverse Proxy

Put Gitea behind Nginx or Caddy for HTTPS.

With Caddy (simplest)

git.yourdomain.com {
    reverse_proxy localhost:3000
}

With Nginx

server {
    listen 443 ssl;
    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.pem;

    # Increase for large repo pushes:
    client_max_body_size 512m;

    location / {
        proxy_pass http://localhost: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 $scheme;
    }
}

Part 4: SSH Access

Gitea handles Git over SSH through port 2222. Users add their SSH key in Settings → SSH/GPG Keys, then clone:

# Clone using Gitea's SSH (port 2222):
git clone ssh://git@git.yourdomain.com:2222/username/repo.git

# Or configure SSH shorthand in ~/.ssh/config:
Host gitea
    HostName git.yourdomain.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_ed25519

# Then clone with:
git clone gitea:username/repo.git

SSH on Port 22 (Optional)

If you want standard SSH cloning (git@git.yourdomain.com:user/repo.git), forward port 22 to the container:

ports:
  - "22:22"  # Change host SSH to a different port first!

Or use Gitea's SSH passthrough mode — see the Gitea docs.


Part 5: Gitea Actions (CI/CD)

Gitea Actions is GitHub Actions-compatible. Your existing .github/workflows/*.yml files work with minor path adjustments.

Enable Gitea Actions

In Gitea admin panel → Site AdministrationConfiguration → Enable Actions.

Or via app.ini:

[actions]
ENABLED = true

Install act_runner

Gitea Actions requires an external runner (act_runner):

# Install act_runner on your VPS or a separate server:
curl -L https://gitea.com/gitea/act_runner/releases/download/latest/act_runner-linux-amd64 \
  -o /usr/local/bin/act_runner
chmod +x /usr/local/bin/act_runner

# Register with your Gitea instance:
act_runner register \
  --instance https://git.yourdomain.com \
  --token YOUR_RUNNER_TOKEN \  # From Gitea Admin → Runners
  --name my-runner \
  --labels ubuntu-latest:docker://node:20

# Run as a service:
act_runner daemon

Or via Docker Compose, add a runner service:

  gitea-runner:
    image: gitea/act_runner:latest
    environment:
      GITEA_INSTANCE_URL: https://git.yourdomain.com
      GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_TOKEN}
      GITEA_RUNNER_NAME: docker-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - runner_data:/data
    depends_on:
      - gitea

Workflow Example

# .gitea/workflows/ci.yml  (or .github/workflows/ci.yml — both work)
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install and test
        run: |
          npm ci
          npm test

Part 6: Migrate from GitHub

Option A: Mirror a Repository

Gitea can mirror GitHub repos (stays in sync automatically):

  1. In Gitea: + New RepositoryMigrate
  2. Select GitHub as source
  3. Enter repo URL and personal access token
  4. Enable Mirror checkbox
  5. Set sync interval (every hour, etc.)

Option B: One-Time Migration (Full History)

# Clone from GitHub with all history:
git clone --mirror https://github.com/username/repo.git

# Push to Gitea:
cd repo.git
git remote add gitea https://git.yourdomain.com/username/repo.git
git push gitea --mirror

Migrate Issues and Pull Requests

Gitea has a built-in GitHub migrator that transfers issues, pull requests, labels, and milestones:

  1. + New RepositoryMigrateGitHub
  2. Enter your GitHub personal access token
  3. Check Issues, Pull Requests, Labels, Milestones
  4. Click Migrate Repository

Part 7: Container Registry

Gitea includes a built-in container registry (Docker/OCI compatible):

# Login to Gitea's container registry:
docker login git.yourdomain.com -u your-username

# Tag and push an image:
docker tag myapp:latest git.yourdomain.com/username/myapp:latest
docker push git.yourdomain.com/username/myapp:latest

In a Gitea Actions workflow:

- name: Build and push Docker image
  run: |
    echo "${{ secrets.GITEA_TOKEN }}" | docker login git.yourdomain.com -u ${{ gitea.actor }} --password-stdin
    docker build -t git.yourdomain.com/${{ gitea.repository }}:latest .
    docker push git.yourdomain.com/${{ gitea.repository }}:latest

Maintenance

Updates

cd /path/to/gitea-compose
docker compose pull
docker compose up -d

Gitea has excellent backward compatibility — updates rarely require manual intervention.

Backups

# Backup Gitea data (inside running container):
docker exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /tmp/gitea-backup.zip

# Copy backup out:
docker cp gitea:/tmp/gitea-backup.zip ./backups/gitea-$(date +%Y%m%d).zip

# Backup Postgres:
docker exec db pg_dump -U gitea gitea | gzip > ./backups/gitea-db-$(date +%Y%m%d).sql.gz

Monitoring

Gitea exposes Prometheus metrics at /metrics (enable in app.ini):

[metrics]
ENABLED = true
TOKEN = your-metrics-token

Resource Usage in Practice

Instance TypeRAM UsageUsersRepos
Personal~40MB1Any
Small team~150MB5–20Any
Medium org~400MB50–100Any
Large org~1GB500+Any

Gitea is exceptionally lean. Compare this to GitLab CE's minimum of 4GB RAM.


See all open source GitHub alternatives at OSSAlt.com/alternatives/github.

Comments