Self-Hosting Gitea and Forgejo on Linux VPS: Complete GitHub Alternative with CI/CD Runners

Modern software engineering organizations are increasingly confronting the hidden operational friction of proprietary SaaS code hosting platforms, ranging from unpredictable per-seat subscription price hikes and invasive telemetry collection to unannounced API rate-limiting and shared CI/CD build queue congestion during peak release windows. While bootstrapping lightweight web applications and experimental microservices on staging platforms like CpanelFree demonstrates the agility of modern hosting stacks, migrating mission-critical source code repositories to a dedicated Linux virtual server reclaims absolute data sovereignty and developer control. By deploying an enterprise-grade self host gitea forgejo vps github alternative powered by hardware-accelerated NVMe storage, PostgreSQL 16, and dedicated Act Runner daemon clusters, engineering teams achieve near-instantaneous Git packfile negotiation and sub-second CI/CD pipeline triggers without recurring software licensing overhead.

What is the Architecture and Performance Advantage of Self-Hosting Gitea or Forgejo on Linux VPS?

Direct Answer: Self-hosting Gitea or Forgejo on an NVMe Linux VPS delivers a complete, lightweight GitHub alternative running on under 300MB of RAM. Integrated with PostgreSQL 16 and Forgejo/Gitea Act Runners, it provides native GitHub Actions-compatible CI/CD pipelines, sub-second Git over SSH operations, and complete data sovereignty without per-seat licensing costs.

For more than a decade, the developer tooling landscape forced an artificial binary choice: teams either accepted the terms, compliance risks, and escalating costs of commercial SaaS repositories like GitHub and GitLab.com, or they shouldered the massive resource footprint of hosting self-managed GitLab Community Edition (CE). GitLab CE’s multi-layered Ruby on Rails architecture, Sidekiq workers, Redis queues, Puma web servers, and Gitaly storage daemons regularly demand 4GB to 8GB of baseline RAM before handling a single concurrent git push operation.

In contrast, Gitea and its community-governed hard fork, Forgejo, are compiled as unified, single-binary Go applications with an idle memory profile under 150MB. By embedding an efficient Go-native HTTP/SSH server, optimized connection multiplexing, and lightweight template rendering into a single static executable, Gitea and Forgejo transform affordable Linux VPS nodes into industrial-grade forge platforms capable of serving hundreds of active developers at line-rate speed.

Architectural Comparison: Gitea vs. Forgejo vs. GitLab vs. SaaS GitHub

Understanding the architectural distinction between Gitea and Forgejo is vital when architecting an enterprise deployment. Forgejo was established in late 2022 as a community-driven fork overseen by Codeberg e.V., designed to guarantee that the platform remains 100% free software under the copyleft GPL-3.0-or-later license, immune to commercial takeover or proprietary relicensing. Gitea continues development under the MIT license managed by Gitea Ltd. Both platforms share deep functional parity, including identical database schemas and complete compatibility with the Act Runner CI/CD daemon.

The table below highlights the operational benchmarks and architectural characteristics comparing a tuned Gitea/Forgejo deployment on an NVMe VPS against standard GitLab CE and SaaS GitHub environments:

Feature / Metric Standard / Default Tuned / Production
Idle Memory Footprint 1,200 MB (GitLab CE Baseline) 145 MB (Gitea / Forgejo Go Binary)
Git Push/Clone Latency (100MB Repo) 4.85 seconds (SaaS GitHub CDN) 0.62 seconds (Local NVMe VPS)
CI/CD Runner Start Overhead 45-90s (GitHub Shared Queue Wait) 1.4s (Dedicated Act Runner Container)
Database Backend Storage I/O SQLite3 Single-File Lock Contention PostgreSQL 16 MVCC + Pooler
Max Concurrent SSH Git Operations 40 sessions (Default sshd MaxStartups) 500+ sessions (Builtin Go SSH Server)
Repository Indexing Engine On-Demand Grep (High CPU Spikes) Bleve In-Memory Search Indexer

Architecture Note: When deploying a self host gitea forgejo vps github alternative, Git storage performance is heavily governed by packfile negotiation and delta compression. During simultaneous git fetch and git push bursts across multi-branch continuous integration workloads, storage controller queue depths surge. Hosting the forge data root on direct PCIe NVMe storage with parallel kernel asynchronous I/O completely eliminates repository lock bottlenecks that routinely throttle spinning disks or multi-tenant network-attached cloud storage volumes.

Linux Kernel & Host OS Optimization for Concurrent Git Workloads

Serving hundreds of developer Git pushes, automated webhook dispatches, and parallel CI runner jobs requires tuning the Linux kernel to eliminate socket bottlenecks, prevent memory paging stalls, and expand file descriptor tracking.

Create a dedicated sysctl configuration file at /etc/sysctl.d/99-git-server.conf on your VPS:

# /etc/sysctl.d/99-git-server.conf
# Enterprise Linux Kernel Tuning for High-Concurrency Gitea / Forgejo VPS

# 1. Virtual Memory Management & Swappiness Control
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
vm.vfs_cache_pressure = 50

# 2. File Descriptors & Inotify Watchers for Git Objects & Act Runner Daemons
fs.file-max = 2097152
fs.inotify.max_user_instances = 8192
fs.inotify.max_user_watches = 1048576

# 3. High-Throughput Network Socket Buffer Allocation
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 4. Connection Tracking & Fast Socket Recycling
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 1024 65535
net.netfilter.nf_conntrack_max = 524288

Apply these parameters immediately without rebooting by executing:

sysctl --system

High-Performance Database Tier: PostgreSQL 16 Optimization

While Gitea and Forgejo allow quick prototyping using an embedded SQLite database, SQLite uses file-level locking that causes fatal concurrency lockouts under parallel Git pushes, automated webhook triggers, and active CI/CD runner heartbeats. For production, PostgreSQL 16 is mandatory.

Provision the dedicated PostgreSQL database and role:

sudo -u postgres psql -c "CREATE USER git WITH ENCRYPTED PASSWORD 'StrongProductionDBPasswordHere';"
sudo -u postgres psql -c "CREATE DATABASE giteadb OWNER git ENCODING 'UTF8' LC_COLLATE 'C.UTF-8' LC_CTYPE 'C.UTF-8' TEMPLATE template0;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE giteadb TO git;"

Next, tune PostgreSQL 16 specifically for high-speed NVMe storage by creating /etc/postgresql/16/main/conf.d/99-gitea-nvme.conf:

# /etc/postgresql/16/main/conf.d/99-gitea-nvme.conf
# Tuned for 4GB-8GB RAM NVMe VPS Hosting Gitea / Forgejo

# Memory Allocation
shared_buffers = 1024MB
work_mem = 32MB
maintenance_work_mem = 256MB
effective_cache_size = 3072MB

# Write-Ahead Log (WAL) & Checkpoints
wal_buffers = 16MB
min_wal_size = 1GB
max_wal_size = 4GB
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min

# NVMe Storage Optimizations
random_page_cost = 1.1
effective_io_concurrency = 200

# Concurrency & Connections
max_connections = 150
synchronous_commit = off

Reload PostgreSQL to enforce the low-latency disk scheduler settings:

systemctl restart postgresql

Enterprise Production Gitea / Forgejo `app.ini` Configuration

The core configuration file for both Gitea and Forgejo resides at /etc/forgejo/app.ini (or /etc/gitea/app.ini). Below is a complete, production-hardened configuration file optimized for native SSH performance, NVMe storage paths, integrated Git LFS, full-text code search, and native Actions support:

# /etc/forgejo/app.ini (or /etc/gitea/app.ini)
# Production-Hardened Architecture for High-Performance Git Hosting

APP_NAME = Enterprise Git Forge
RUN_USER = git
WORK_PATH = /var/lib/forgejo
RUN_MODE = prod

[server]
PROTOCOL = http
DOMAIN = git.example.com
ROOT_URL = https://git.example.com/
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
DISABLE_SSH = false
START_SSH_SERVER = true
SSH_PORT = 2222
SSH_LISTEN_PORT = 2222
SSH_SERVER_CIPHERS = [email protected],[email protected]
OFFLINE_MODE = false
LANDING_PAGE = explore
LFS_START_SERVER = true
LFS_MAX_FILE_SIZE = 5368709120

[database]
DB_TYPE = postgres
HOST = 127.0.0.1:5432
NAME = giteadb
USER = git
PASSWD = StrongProductionDBPasswordHere
SCHEMA = public
SSL_MODE = disable
MAX_OPEN_CONNS = 50
MAX_IDLE_CONNS = 20
CONN_MAX_LIFETIME = 300s

[repository]
ROOT = /var/lib/forgejo/data/forgejo-repositories
SCRIPT_TYPE = bash
DEFAULT_PRIVATE = private
MAX_CREATION_LIMIT = -1
DEFAULT_BRANCH = main
ENABLE_PUSH_CREATE_USER = true
ENABLE_PUSH_CREATE_ORG = true

[actions]
ENABLED = true

[packages]
ENABLED = true

[lfs]
PATH = /var/lib/forgejo/data/lfs
STORAGE_TYPE = local

[indexer]
ISSUE_INDEXER_TYPE = bleve
REPO_INDEXER_ENABLED = true
REPO_INDEXER_TYPE = bleve
REPO_INDEXER_PATH = /var/lib/forgejo/indexers/repos.bleve
REPO_INDEXER_EXCLUDE = **/.git/**,**/vendor/**,**/node_modules/**
MAX_FILE_SIZE = 1048576

[cache]
ADAPTER = memory
INTERVAL = 60

[session]
PROVIDER = memory

[security]
INSTALL_LOCK = true
SECRET_KEY = !REPLACE_WITH_A_64_CHAR_RANDOM_HEX_KEY!
INTERNAL_TOKEN = !REPLACE_WITH_A_64_CHAR_RANDOM_HEX_KEY!
DISABLE_GIT_HOOKS = false

Systemd Service Hardening & Sandboxing

Running forge services with root privileges or inadequate process isolation exposes the host OS to severe container breakout vulnerabilities. The systemd service must enforce Linux kernel namespaces, restrict device nodes, and prevent privilege escalation.

Create the hardened unit file at /etc/systemd/system/forgejo.service:

[Unit]
Description=Forgejo Git Forge Service
After=network.target postgresql.service
Wants=postgresql.service

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

# Systemd Security Hardening & Isolation
LimitNOFILE=65535
LimitNPROC=16384
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/forgejo /var/log/forgejo /etc/forgejo
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictRealtime=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Enable and start the service with standard systemd controls:

systemctl daemon-reload
systemctl enable --now forgejo

Self-Hosted CI/CD: Setting Up Act Runner with Container Isolation

One of the most powerful features of modern Gitea and Forgejo is Act Runner. Act Runner is an independent Go daemon based on the open-source nektos/act engine that enables self-hosted forges to interpret and execute standard GitHub Actions workflows (.github/workflows/*.yaml or .forgejo/workflows/*.yaml) with zero modifications.

Act Runner communicates with your Gitea/Forgejo instance via long-polling gRPC over HTTPS, pulling queued build jobs and provisioning transient Docker or Podman containers on demand.

First, install Docker Engine or Podman on your Linux VPS. Then download and place the latest act_runner binary in /usr/local/bin/act_runner. Generate the default runner configuration:

act_runner generate-config > /etc/act_runner/config.yaml

Customize /etc/act_runner/config.yaml to tune container execution concurrency and local caching:

# /etc/act_runner/config.yaml
# Production Configuration for Self-Hosted Forgejo / Gitea Act Runner

runner:
  capacity: 4
  timeout: 3h
  insecure: false
  fetch_timeout: 5s
  fetch_interval: 2s
  labels:
    - "ubuntu-latest:docker://node:20-bookworm"
    - "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
    - "rocky-9:docker://rockylinux:9"
    - "linux-arm64:docker://catthehacker/ubuntu:act-latest-arm64"

cache:
  enabled: true
  dir: /var/lib/act_runner/cache
  host: ""
  port: 0

container:
  network: "bridge"
  privileged: false
  options: "--cpus=2 --memory=2g"
  valid_volumes:
    - "/var/run/docker.sock"
  docker_host: ""

Obtain a registration token from your forge administrator panel (or repository settings under Actions > Runners > Create New Runner) and register the daemon:

act_runner register   --instance https://git.example.com   --token YOUR_GENERATED_REGISTRATION_TOKEN_HERE   --name "vps-production-runner-01"   --config /etc/act_runner/config.yaml   --no-interactive

Finally, create a systemd service at /etc/systemd/system/act-runner.service to supervise the runner daemon:

[Unit]
Description=Act Runner for Gitea/Forgejo CI/CD
After=network.target docker.service
Wants=docker.service

[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/act_runner
ExecStart=/usr/local/bin/act_runner daemon --config /etc/act_runner/config.yaml
Restart=always
RestartSec=5s
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

Start the runner daemon:

systemctl daemon-reload
systemctl enable --now act-runner

Production CI/CD Pipeline Workflow Example

With Act Runner active, you can drop standard GitHub Actions syntax into your repository at .forgejo/workflows/ci.yaml (or .gitea/workflows/ci.yaml). The following workflow executes parallel testing, builds an OCI container image, and pushes it to Forgejo’s integrated container registry:

# .forgejo/workflows/ci.yaml
# Production CI/CD Pipeline with Integrated Package Registry Deployment

name: Enterprise Build & Deploy Matrix

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test-and-lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v4

      - name: Setup Go Toolchain
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          cache: true

      - name: Verify Dependencies
        run: go mod verify

      - name: Execute Automated Unit Tests
        run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./...

  build-and-publish:
    needs: test-and-lint
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Log In to Internal Forge Container Registry
        uses: docker/login-action@v3
        with:
          registry: git.example.com
          username: ${{ github.actor }}
          password: ${{ secrets.FORGE_TOKEN }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            git.example.com/${{ github.repository }}:latest
            git.example.com/${{ github.repository }}:${{ github.sha }}

When running mission-critical engineering workflows, distributed team collaboration repositories, and high-frequency automated build matrices that demand dedicated CPU cores, zero virtualization jitter, and sustained 100% NVMe storage IOPS, hosting your production code repositories on MeraHost Enterprise Cloud guarantees carrier-grade network transit, guaranteed dedicated hardware allocations, and fixed billing with Same Renewal Price, Always (starting at ₹99/mo).

Actionable FAQs: Self-Hosting Gitea and Forgejo on Linux VPS

Should I choose Gitea or Forgejo for a new production deployment in 2026?

For long-term community stewardship and strict adherence to open-source governance, Forgejo is the recommended choice. Because Forgejo is backed by the non-profit Codeberg e.V. and governed under the copyleft GPL-3.0 license, it is protected from commercial relicensing or feature gating. If you require commercial enterprise support contracts backed by a corporate entity, Gitea Ltd. provides paid SLA tiers for Gitea. Both platforms share deep architectural parity and use identical Act Runner CI/CD binaries.

Can Forgejo and Gitea Actions run unmodified GitHub Actions from the GitHub Marketplace?

Yes. Act Runner natively understands standard GitHub Actions YAML syntax. However, because actions like actions/checkout@v4 or docker/login-action@v3 are hosted on GitHub, Act Runner by default fetches them from https://github.com. In air-gapped or private enterprise environments, you can mirror these action repositories directly to your local Forgejo/Gitea instance and configure act_runner to resolve actions locally by setting prefix mappings in config.yaml.

How do I prevent repository bloat when developers commit large binary files?

Enable Git Large File Storage (Git LFS) in your app.ini by setting LFS_START_SERVER = true under [server]. Git LFS replaces multi-megabyte binary assets (such as container archives, machine learning weights, compiled binaries, and design files) with tiny text pointer files inside the Git tree, storing the raw blobs in a separate dedicated storage path (/var/lib/forgejo/data/lfs) or offloading them directly to S3-compatible object storage.

What is the recommended disaster recovery and automated backup strategy?

Automate a daily snapshot script using the built-in backup CLI: forgejo dump --file /backup/forgejo-backup-$(date +%F).zip --config /etc/forgejo/app.ini. This utility pauses write operations, executes a consistent pg_dump of the PostgreSQL database, and archives all Git repositories, LFS assets, custom configuration files, and avatars into an encrypted, portable ZIP archive ready for off-site rsync transfer.

Deploy Enterprise-Grade Production Infrastructure

Need guaranteed performance with zero price hikes? Host mission-critical workloads on MeraHost with pure Enterprise NVMe, LiteSpeed Web Server, and Same Renewal Price, Always (starting at ₹99/mo).

🚀 Production Cloud Infrastructure

Deploy on High-Performance Cloud VPS with MeraHost

Need dedicated vCPU, root SSH access, pure NVMe SSD speeds, and 99.95% uptime SLA for your production stack? Launch with MeraHost — guaranteed price lock with zero renewal hikes.

Root SSH Access Dedicated vCPU & RAM Pure NVMe Storage 99.95% Uptime SLA
⚡ Instant Provisioning · 🔒 Price Lock Guarantee · 🌐 Global Datacenters

Leave a Comment