Gitea: Lightweight GitHub Alternative That Actually Runs Anywhere

Wanted to move off GitHub for privacy reasons, but GitLab needed 4GB RAM minimum. Found Gitea - runs on a Raspberry Pi, handles 100+ repos, and feels just like GitHub. Been running it for 8 months on a $5 VPS with zero issues.

Problem

Set up Gitea, created a repo, added my SSH key. Could clone via SSH just fine, but pushing failed with "repository not found". HTTPS push worked, but SSH didn't. Double-checked permissions - everything looked correct.

Error: fatal: repository 'username/repo.git' not found

What I Tried

Attempt 1: Re-added SSH key - same error.
Attempt 2: Changed repo permissions to public - still failed.
Attempt 3: Tried pushing with verbose SSH - showed authentication succeeded but then failed.

Actual Fix

The issue was that Gitea's SSH server was listening on port 2222 (not default 22), but my git config was using default port. Also, the SSH key needed to be added to my user profile, not just the admin SSH keys section. The authentication was succeeding (that's why no auth error), but Gitea couldn't find the repo due to user mismatch.

# 1. Check which port Gitea SSH is using
docker exec gitea grep "SSH_PORT" /data/gitea/conf/app.ini

# 2. Configure SSH to use correct port
# In ~/.ssh/config, add:
Host git.yourdomain.com
    HostName git.yourdomain.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_rsa

# 3. Add SSH key to your user profile (not admin keys)
# Gitea UI: Settings → SSH Keys → Add Key
# Paste your public key

# 4. Test SSH connection
ssh -T git@yourdomain.com -p 2222
# Should show: "Hi username! You've successfully authenticated"

# 5. Test push
git remote set-url origin git@yourdomain.com:username/repo.git
git push origin main

# Alternative: Use HTTPS if SSH is problematic
git remote set-url origin https://yourdomain.com/username/repo.git

Problem

Set up a webhook to trigger a CI pipeline on pushes. Webhook showed "last delivery: never" even after multiple pushes. Manual trigger from Gitea UI worked fine, but automatic triggers on push events didn't fire.

What I Tried

Attempt 1: Deleted and recreated webhook - same issue.
Attempt 2: Changed trigger to "all events" - still nothing.
Attempt 3: Checked CI server logs - no incoming requests at all.

Actual Fix

The issue was in Gitea's app.ini configuration. Webhooks were disabled by default, or the queue worker wasn't running properly. Also, some webhook types need specific permissions to access repository data.

# In Gitea app.ini, enable webhooks:
# [server section]
[server]
LFS_START_SERVER = true
LFS_JWT_SECRET = your_secret_here

# [webhook section]
[webhook]
QUEUE_LENGTH = 1000
DELIVER_TIMEOUT = 10
SKIP_TLS_VERIFY = false

# Enable webhook types
[webhook.issues]
ENABLED = true

[webhook.pull_request]
ENABLED = true

# Restart Gitea
docker restart gitea

# Or via docker-compose:
docker-compose exec gitea /app/gitea/webhook

# Verify webhook is enabled:
docker exec gitea /app/gitea doctor check
# Should show webhooks as "OK"

# Test webhook manually:
curl -X POST \
  http://your-gitea-url/api/v1/repos/username/repo/hooks/test \
  -H "Authorization: token your_access_token"

Problem

Set up Gitea to mirror a GitHub repo. Initial sync worked fine, but subsequent updates failed randomly. Sometimes it would sync, sometimes it would timeout. Mirror status showed "syncing" forever.

Error: fatal: unable to access 'https://github.com/...': Failed to connect to github.com port 443

What I Tried

Attempt 1: Re-authenticated with GitHub personal access token - still intermittent.
Attempt 2: Increased sync interval - didn't help, just delayed the problem.
Attempt 3: Checked network connectivity - could ping github.com fine.

Actual Fix

The issue was that Gitea's mirror sync was using the wrong authentication method for GitHub. Also, sync timeouts were too aggressive for large repos. Needed to configure proper credentials and increase timeout values in app.ini.

# 1. Set up mirror credentials properly
# In Gitea UI: Repository Settings → Mirror Sync
# Use GitHub Personal Access Token with 'repo' scope

# 2. Configure mirror settings in app.ini
# [mirror section]
[mirror]
ENABLED = true
MIN_INTERVAL = 10m
DEFAULT_INTERVAL = 1h

# [git section]
[git]
MAX_GIT_DIFF_LINES = 10000
GIT_ARGS = --timeout=600  # 10 minutes for large repos

# 3. For reliable mirroring, use SSH instead of HTTPS
# Generate SSH key for Gitea user
docker exec gitea gitea generate git

# Add public key to GitHub: Settings → SSH Keys

# Update mirror URL to use SSH:
# git@github.com:username/repo.git

# 4. Force manual sync if needed
docker exec gitea /app/gitea mirror sync

# 5. Check mirror status
docker exec gitea /app/gitea mirror list

What I Learned

Production Setup

Complete Docker Compose setup with PostgreSQL.

# docker-compose.yml for Gitea
version: "3.8"

services:
  gitea-db:
    image: postgres:14-alpine
    container_name: gitea-db
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=gitea
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - gitea-network
    restart: always

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=postgres
      - DB_HOST=gitea-db:5432
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWD=${DB_PASSWORD}
      - SSH_DOMAIN=git.yourdomain.com
      - SSH_PORT=2222
      - HTTP_PORT=3000
      - ROOT_URL=https://git.yourdomain.com
      - LFS_START_SERVER=true
    ports:
      - "2222:2222"
      - "127.0.0.1:3000:3000"
    volumes:
      - ./data/gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    depends_on:
      - gitea-db
    networks:
      - gitea-network
    restart: always

networks:
  gitea-network:
# .env file
echo "DB_PASSWORD=$(openssl rand -base64 32)" > .env

# Start Gitea
docker-compose up -d

# Access web UI at: http://localhost:3000
# Or via reverse proxy: https://git.yourdomain.com

# Initial setup (first visit):
# - Database Type: PostgreSQL
# - Host: gitea-db:5432
# - User: gitea
# - Password: (from .env)
# - Database Name: gitea

# Admin account creation
# - Username: admin
# - Password: (choose strong password)
# - Email: admin@yourdomain.com

# Configure reverse proxy (Nginx example)
server {
    listen 443 ssl http2;
    server_name git.yourdomain.com;

    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;
    }

    # WebSocket support for git operations
    location ~ ^/([^/]+\.git(/info/refs)?|.*/git-upload-pack\.git)$ {
        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;
    }
}

Monitoring & Debugging

Keep your Gitea instance healthy:

Essential Commands

# Check Gitea logs
docker logs -f gitea

# Update Gitea
docker-compose pull
docker-compose up -d

# Backup database
docker exec gitea-db pg_dump -U gitea gitea | gzip > gitea_backup.sql.gz

# Backup data directory
tar -czf gitea_data_backup.tar.gz ./data/gitea

# Check SSH connectivity
ssh -T -p 2222 git@git.yourdomain.com

# Run Gitea doctor (health check)
docker exec gitea gitea doctor check

# Manage users via CLI
docker exec -u git gitea gitea admin user list
docker exec -u git gitea gitea admin user create --username newuser --email user@example.com --password password

# Resync all repositories
docker exec gitea /app/gitea repo-sync re-mirror

Red Flags to Watch For

Related Resources