Press ESC to close Press / to search

Forgejo: Self-Hosted GitHub Alternative on Linux — Complete Setup and Migration Guide

🎯 Key Takeaways

  • Table of Contents
  • Forgejo vs Gitea vs GitLab: Which to Choose
  • System Requirements
  • Install Forgejo with Docker Compose
  • Install Forgejo as a Native Binary

📑 Table of Contents

Forgejo is a self-hosted Git platform forked from Gitea after the Gitea development team formed a private limited company in 2022. It gives you a complete GitHub-like experience — repositories, pull requests, issues, CI/CD, container registry, and package hosting — running entirely on infrastructure you control. This guide covers deploying Forgejo on a Linux server using Docker or a native binary, configuring it for production use, and migrating repositories from GitHub, GitLab, or Gitea.

Table of Contents

Forgejo vs Gitea vs GitLab: Which to Choose

Forgejo and Gitea share the same codebase origin. Forgejo is a community-governed soft fork maintained by the Codeberg e.V. non-profit — it will always remain fully open source under the MIT license with no commercial entity controlling its roadmap. Gitea, while still open source, is now developed by a company with a commercial version in mind. For teams that need a lightweight, fully open, self-hosted Git platform, Forgejo is the recommended choice.

Feature Forgejo Gitea GitLab CE
Governance Non-profit, community Commercial company Commercial (GitLab Inc.)
Resource usage ~150 MB RAM idle ~150 MB RAM idle ~4 GB RAM minimum
Built-in CI/CD Forgejo Actions (GitHub-compatible) Gitea Actions GitLab CI/CD
Container registry Yes Yes Yes
API compatibility GitHub REST API compatible GitHub REST API compatible GitLab API

System Requirements

Forgejo is exceptionally lightweight. A VPS with 1 vCPU and 512 MB RAM runs it comfortably for teams of up to 10–20 users. For larger teams or heavy CI/CD workloads, 2 vCPUs and 2 GB RAM is comfortable. Storage depends on repository sizes — allocate at least 20 GB for the data volume.

Install Forgejo with Docker Compose

mkdir -p /opt/forgejo && cd /opt/forgejo

cat > docker-compose.yml << 'COMPOSE'
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:9
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=postgres
      - FORGEJO__database__HOST=forgejo-db:5432
      - FORGEJO__database__NAME=forgejo
      - FORGEJO__database__USER=forgejo
      - FORGEJO__database__PASSWD=changeme_strong_password
    ports:
      - "3000:3000"     # Web UI (proxied by nginx)
      - "222:22"        # SSH Git access
    volumes:
      - ./data:/data
    depends_on:
      - forgejo-db

  forgejo-db:
    image: postgres:16-alpine
    container_name: forgejo-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=changeme_strong_password
      - POSTGRES_DB=forgejo
    volumes:
      - ./postgres:/var/lib/postgresql/data
COMPOSE

docker compose up -d
docker compose logs -f forgejo

Install Forgejo as a Native Binary

# Get the latest version
FORGEJO_VERSION=$(curl -s https://codeberg.org/api/v1/repos/forgejo/forgejo/releases \
  | python3 -c "import sys,json; r=json.load(sys.stdin); print(r[0]['tag_name'].lstrip('v'))")

ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')

wget "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${ARCH}"
chmod +x forgejo-${FORGEJO_VERSION}-linux-${ARCH}
mv forgejo-${FORGEJO_VERSION}-linux-${ARCH} /usr/local/bin/forgejo

# Create system user and directories
useradd --system --create-home --home-dir /var/lib/forgejo \
  --shell /usr/sbin/nologin git
mkdir -p /etc/forgejo /var/lib/forgejo/{custom,data,log}
chown -R git:git /var/lib/forgejo /etc/forgejo
chmod 750 /etc/forgejo

# Create systemd service
cat > /etc/systemd/system/forgejo.service << 'UNIT'
[Unit]
Description=Forgejo Self-Hosted Git Service
After=network.target postgresql.service

[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/forgejo
ExecStart=/usr/local/bin/forgejo web --config /etc/forgejo/app.ini
Restart=on-failure
RestartSec=5s
Environment=USER=git HOME=/var/lib/forgejo GITEA_WORK_DIR=/var/lib/forgejo

[Install]
WantedBy=multi-user.target
UNIT

systemctl daemon-reload
systemctl enable --now forgejo

Configure Nginx Reverse Proxy with TLS

# Get TLS certificate first
certbot certonly --standalone -d git.example.com \
  --email admin@example.com --agree-tos -n

# Nginx configuration
cat > /etc/nginx/sites-available/forgejo << 'NGINX'
server {
    listen 80;
    server_name git.example.com;
    return 301 https://$host$request_uri;
}

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

    ssl_certificate     /etc/letsencrypt/live/git.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    client_max_body_size 512m;   # Allow large repository 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 $scheme;
    }
}
NGINX

ln -s /etc/nginx/sites-available/forgejo /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Initial Setup and Admin Account

Visit https://git.example.com in your browser. The installation wizard walks you through:

  1. Database settings (PostgreSQL recommended)
  2. Base URL (must match your domain exactly)
  3. Email settings for notifications
  4. Admin username, password, and email
# Or create admin account via CLI (useful for automated setups)
forgejo admin user create \
  --username admin \
  --password 'StrongPassword123!' \
  --email admin@example.com \
  --admin \
  --config /etc/forgejo/app.ini

Using PostgreSQL as the Database

# Install and configure PostgreSQL on RHEL/Rocky
dnf install -y postgresql-server postgresql-contrib
postgresql-setup --initdb
systemctl enable --now postgresql

# Create Forgejo database and user
sudo -u postgres psql << 'SQL'
CREATE USER forgejo WITH PASSWORD 'strong_password_here';
CREATE DATABASE forgejo OWNER forgejo;
GRANT ALL PRIVILEGES ON DATABASE forgejo TO forgejo;
SQL

# Key settings in /etc/forgejo/app.ini for PostgreSQL
cat >> /etc/forgejo/app.ini << 'INI'
[database]
DB_TYPE  = postgres
HOST     = 127.0.0.1:5432
NAME     = forgejo
USER     = forgejo
PASSWD   = strong_password_here
SSL_MODE = disable
INI

Configuring SSH Access for Git Operations

# Forgejo native binary: SSH runs on port 22 by default
# If system SSH also uses port 22, run Forgejo SSH on a different port

# In /etc/forgejo/app.ini:
cat >> /etc/forgejo/app.ini << 'INI'
[server]
SSH_DOMAIN       = git.example.com
SSH_PORT         = 22
START_SSH_SERVER = true    # Use Forgejo's built-in SSH server
SSH_LISTEN_PORT  = 2222    # Forgejo SSH on 2222 if system SSH uses 22
INI

# For Docker: port 222 on host maps to 22 in container
# Users clone with: git clone ssh://git@git.example.com:222/user/repo.git

# Add SSH public key in Forgejo: User Settings → SSH Keys → Add Key

Forgejo Actions: Built-in CI/CD

Forgejo Actions uses the same workflow YAML syntax as GitHub Actions. Existing GitHub Actions workflows run on Forgejo with minimal changes.

# Enable Actions in /etc/forgejo/app.ini
cat >> /etc/forgejo/app.ini << 'INI'
[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://code.forgejo.org  # Mirror of github.com/actions
INI

# Deploy an Actions runner on any Linux host
# Download runner
wget https://code.forgejo.org/forgejo/runner/releases/download/v3.5.0/forgejo-runner-3.5.0-linux-amd64
chmod +x forgejo-runner-3.5.0-linux-amd64
mv forgejo-runner-3.5.0-linux-amd64 /usr/local/bin/forgejo-runner

# Register the runner with your Forgejo instance
# Get a registration token from: Site Admin → Runners → Create Runner Token
forgejo-runner register \
  --instance https://git.example.com \
  --token YOUR_REGISTRATION_TOKEN \
  --name my-runner \
  --labels linux,docker

# Start the runner
forgejo-runner daemon
# Example Forgejo Actions workflow (.forgejo/workflows/ci.yml)
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: docker
    container:
      image: node:20-alpine
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
      - name: Build
        run: npm run build

Container Registry and Package Hosting

# Enable the container registry in app.ini
cat >> /etc/forgejo/app.ini << 'INI'
[packages]
ENABLED = true
INI

# Push container images to Forgejo's registry
docker login git.example.com
docker tag myapp:latest git.example.com/username/myapp:latest
docker push git.example.com/username/myapp:latest

# Pull from Forgejo registry
docker pull git.example.com/username/myapp:latest

Migrating Repositories from GitHub and GitLab

# Method 1: Forgejo web UI migration (easiest)
# → "+" → "Migrate External Wiki" → select GitHub/GitLab
# → Enter source URL, credentials, and migration options
# Forgejo mirrors the repo and can continue syncing changes

# Method 2: CLI bulk migration using the API
# Create a migration for each repo
for repo in repo1 repo2 repo3; do
  curl -X POST "https://git.example.com/api/v1/repos/migrate" \
    -H "Authorization: token YOUR_FORGEJO_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "clone_addr": "https://github.com/yourorg/'"$repo"'",
      "auth_token": "YOUR_GITHUB_PAT",
      "repo_name": "'"$repo"'",
      "uid": 1,
      "mirror": false,
      "private": false
    }'
done

Backup and Restore

# Forgejo includes a built-in backup command
forgejo admin app generate-config \
  --work-path /var/lib/forgejo \
  --config /etc/forgejo/app.ini

# Create a full backup (database + repos + config + attachments)
forgejo admin dump \
  --config /etc/forgejo/app.ini \
  --file /backup/forgejo-$(date +%Y%m%d).zip \
  --type zip

# Restore from backup
# 1. Stop Forgejo
systemctl stop forgejo
# 2. Extract backup
unzip /backup/forgejo-20260410.zip -d /var/lib/forgejo/
# 3. Restore database (PostgreSQL example)
psql -U forgejo forgejo < forgejo-db.sql
# 4. Fix permissions and start
chown -R git:git /var/lib/forgejo
systemctl start forgejo

Security Hardening

# Key settings in /etc/forgejo/app.ini for production hardening

cat >> /etc/forgejo/app.ini << 'INI'
[server]
DISABLE_REGISTRATION = true    # Prevent public signups
REQUIRE_SIGNIN_VIEW  = true    # Require login to view repos

[security]
INSTALL_LOCK          = true   # Prevent re-running installer
SECRET_KEY            = 
INTERNAL_TOKEN        = 
PASSWORD_HASH_ALGO    = argon2  # Stronger than bcrypt default
MIN_PASSWORD_LENGTH   = 16

[service]
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA        = true   # For public registration if enabled

[session]
PROVIDER = db
COOKIE_SECURE = true           # HTTPS only cookies
INI

systemctl restart forgejo

Conclusion

Forgejo delivers everything teams need from a hosted Git platform — repositories, code review, issues, CI/CD pipelines, container registry — at a fraction of GitLab's resource requirements and with zero dependence on any commercial platform. The GitHub Actions-compatible workflow syntax means you can lift and shift existing CI pipelines directly. For teams prioritizing data sovereignty, Forgejo running on a single 1 GB VPS is a practical, production-ready solution that is genuinely fun to self-host.

Was this article helpful?

Advertisement
🏷️ Tags: ci-cd forgejo forgejo actions git server gitea github alternative linux git self-hosted git
R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Advertisement

Add Comment