Deploy Gitea on a VPS: Self-Hosted Git Repository Platform (2026 Guide)

One-sentence verdict: Gitea gives you a full-featured GitHub alternative on a $3.50–$5/month VPS — with private repos, built-in CI/CD (Gitea Actions), package registries, and complete data ownership — running comfortably on 1 GB RAM.

Who This Guide Is For

If you only need a handful of private repos and basic CI, GitHub’s free tier is simpler. This guide is for when you want full control, unlimited everything, or must keep code off third-party servers.

What Gitea Actually Is

Gitea is a lightweight, self-hosted Git forge written in Go. Think of it as GitHub stripped down to the essentials, then rebuilt to run on minimal hardware. A single binary (or Docker container) gives you:

Unlike GitLab (which wants 4+ GB RAM), Gitea idles at ~100–200 MB and boots in seconds.

Price & Spec Comparison

ProviderPlanRAMStorageMonthly CostBest For
RackNerdKVM 1 GB1 GB20 GB SSD$3.49Solo/hobby use
ContaboVPS S8 GB200 GB NVMe€5.99Teams with many repos
HetznerCX224 GB40 GB€3.99EU-based teams
VultrCloud Compute1 GB25 GB SSD$5.00Global edge locations
DigitalOceanBasic Droplet1 GB25 GB SSD$6.00Managed backups

Minimum viable spec: 1 vCPU, 1 GB RAM, 15 GB storage. Comfortable for 1–5 users with < 50 repos.

Recommended for teams: 2 vCPU, 2–4 GB RAM, 40+ GB storage. Handles 10–20 active users, CI runners, and package registries.

Risk Warnings

Step-by-Step Deployment

Prerequisites

Step 1: Prepare the Server

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install Docker and Docker Compose
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# Install Caddy for automatic HTTPS (simpler than Nginx + Certbot)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy -y

Log out and back in so Docker group takes effect.

Step 2: Create Docker Compose File

mkdir -p ~/gitea && cd ~/gitea

Create docker-compose.yml:

version: "3"

services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=sqlite3
      - GITEA__server__DOMAIN=git.yourdomain.com
      - GITEA__server__ROOT_URL=https://git.yourdomain.com/
      - GITEA__server__SSH_DOMAIN=git.yourdomain.com
      - GITEA__server__SSH_PORT=2222
      - GITEA__service__DISABLE_REGISTRATION=false
      - GITEA__mailer__ENABLED=false
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    restart: unless-stopped

For teams with 10+ users, swap SQLite for PostgreSQL:

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

And update the Gitea environment:

      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=CHANGE_THIS_PASSWORD

Step 3: Configure Caddy for HTTPS

Edit /etc/caddy/Caddyfile:

git.yourdomain.com {
    reverse_proxy localhost:3000
}
sudo systemctl restart caddy

Caddy automatically obtains and renews Let’s Encrypt certificates. No certbot cron needed.

Step 4: Start Gitea

cd ~/gitea
docker compose up -d

Visit https://git.yourdomain.com — you’ll see the initial configuration page. The Docker environment variables pre-fill most settings. Click “Install Gitea” and create your admin account.

Step 5: Harden the Installation

After initial setup, disable public registration:

# Edit docker-compose.yml, change:
# GITEA__service__DISABLE_REGISTRATION=true
docker compose up -d

Configure SSH properly in ~/gitea/data/gitea/conf/app.ini if needed:

[server]
SSH_PORT = 2222
SSH_LISTEN_PORT = 22
START_SSH_SERVER = true

Set up UFW firewall:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw allow OpenSSH
sudo ufw enable

Step 6: Enable Gitea Actions (CI/CD)

Add to your app.ini or Docker environment:

      - GITEA__actions__ENABLED=true

Register a runner on the same server:

docker run -d --name gitea-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e GITEA_INSTANCE_URL=http://gitea:3000 \
  -e GITEA_RUNNER_REGISTRATION_TOKEN=YOUR_TOKEN \
  --network gitea_default \
  gitea/act_runner:latest

Get your registration token from Site Administration → Runners in the Gitea web UI.

Now you can use .gitea/workflows/ files (same syntax as GitHub Actions) in any repo.

Step 7: Set Up Automated Backups

Create ~/gitea/backup.sh:

#!/bin/bash
BACKUP_DIR="$HOME/gitea-backups"
mkdir -p "$BACKUP_DIR"

# Dump Gitea data
docker exec gitea /usr/local/bin/gitea dump -c /data/gitea/conf/app.ini -f /data/gitea-dump.zip

# Copy dump out of container
docker cp gitea:/data/gitea-dump.zip "$BACKUP_DIR/gitea-$(date +%Y%m%d).zip"

# Clean up inside container
docker exec gitea rm /data/gitea-dump.zip

# Keep only last 7 backups locally
find "$BACKUP_DIR" -name "gitea-*.zip" -mtime +7 -delete

# Optional: sync to offsite storage
# rclone copy "$BACKUP_DIR" remote:gitea-backups/
chmod +x ~/gitea/backup.sh
crontab -e
# Add: 0 3 * * * /home/yourusername/gitea/backup.sh

Resource Usage in Practice

Real measurements on a 1 GB RAM / 1 vCPU VPS with 12 repos and 3 active users:

MetricIdleActive (git push + web browsing)
RAM usage180 MB350 MB
CPU< 1%5–15%
Disk (base install)250 MB
Disk (12 repos, 2 GB total)2.3 GB
Boot time3 seconds

Gitea Actions CI runs spike CPU to 80–100% during builds but are short-lived (typically < 2 minutes for standard Node/Python projects).

Gitea vs Alternatives

FeatureGiteaGitLab CEForgejoGitHub Free
Min RAM512 MB4 GB512 MBN/A (SaaS)
Built-in CI✅ (Actions)✅ (Actions)
Package registry
Self-hosted
Private reposUnlimitedUnlimitedUnlimitedUnlimited
LicenseMITMIT (CE)GPLProprietary
Maintenance effortLowHighLowNone
Monthly cost (self-hosted)$3.50–$6$20+ (needs RAM)$3.50–$6Free

Forgejo is a Gitea fork with identical resource requirements. Choose Forgejo if you prefer community governance over corporate backing. The deployment steps are nearly identical (swap gitea/gitea image for codeberg/forgejo).

When NOT to Self-Host Git

What’s Next

Once Gitea is running:

  1. Mirror your GitHub repos — Settings → Mirror from GitHub. Automatic sync keeps a backup.
  2. Set up Gitea Actions — Port your GitHub Actions workflows with minimal changes.
  3. Add package registries — Push npm, Docker, or PyPI packages to your own registry.
  4. Configure Woodpecker CI — Alternative CI that pairs well with Gitea for complex pipelines.
  5. Deploy Gitea Pages — Experimental static site hosting from repos (like GitHub Pages).

Summary

ItemDetail
Total monthly cost$3.50–$6 (VPS only)
Setup time15–20 minutes
Maintenance~10 min/week (updates + backup checks)
Best budget pickRackNerd 1 GB @ $3.49/mo
Best team pickHetzner CX22 @ €3.99/mo
Overkill thresholdWhen your team exceeds 30 people or needs enterprise compliance features