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
- Solo developers and freelancers who want unlimited private repos without paying for GitHub Teams.
- Small dev teams (2–10 people) who need code hosting, issues, and CI without vendor lock-in.
- Hobbyists who want to mirror GitHub repos and keep a personal backup of all their code.
- Companies in regulated industries that require source code to stay on infrastructure they control.
- Self-hosters already running services on a VPS who want to consolidate their dev workflow.
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:
- Git repository hosting with web UI, pull requests, and code review
- Issue tracking with labels, milestones, and project boards
- Gitea Actions (GitHub Actions-compatible CI/CD since v1.19)
- Package registries (npm, PyPI, Docker, Cargo, Maven, and more)
- OAuth2 provider and fine-grained access tokens
- Webhook integrations, API compatibility, and migration tools
Unlike GitLab (which wants 4+ GB RAM), Gitea idles at ~100–200 MB and boots in seconds.
Price & Spec Comparison
| Provider | Plan | RAM | Storage | Monthly Cost | Best For |
|---|---|---|---|---|---|
| RackNerd | KVM 1 GB | 1 GB | 20 GB SSD | $3.49 | Solo/hobby use |
| Contabo | VPS S | 8 GB | 200 GB NVMe | €5.99 | Teams with many repos |
| Hetzner | CX22 | 4 GB | 40 GB | €3.99 | EU-based teams |
| Vultr | Cloud Compute | 1 GB | 25 GB SSD | $5.00 | Global edge locations |
| DigitalOcean | Basic Droplet | 1 GB | 25 GB SSD | $6.00 | Managed 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
- Backups are your responsibility. If the VPS dies and you have no offsite backup, your code is gone. Set up automated backups (covered below).
- Security patching is on you. Gitea releases security fixes regularly. Subscribe to their RSS or watch the GitHub releases page.
- CI runners eat resources. Gitea Actions run on the same server by default. Heavy builds can starve the web UI. Consider a separate runner for production workloads.
- No uptime SLA. Unlike GitHub’s 99.9% SLA, your self-hosted instance depends on your VPS provider and your maintenance.
- SSH key management matters. Misconfigured SSH can lock you out of both the server and Git access simultaneously.
Step-by-Step Deployment
Prerequisites
- A VPS running Ubuntu 22.04+ or Debian 12+ (this guide uses Ubuntu 24.04)
- A domain name pointed at your VPS IP (e.g.,
git.yourdomain.com) - SSH access with a non-root sudo user
- Basic familiarity with Docker and Linux commands
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:
| Metric | Idle | Active (git push + web browsing) |
|---|---|---|
| RAM usage | 180 MB | 350 MB |
| CPU | < 1% | 5–15% |
| Disk (base install) | 250 MB | — |
| Disk (12 repos, 2 GB total) | 2.3 GB | — |
| Boot time | 3 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
| Feature | Gitea | GitLab CE | Forgejo | GitHub Free |
|---|---|---|---|---|
| Min RAM | 512 MB | 4 GB | 512 MB | N/A (SaaS) |
| Built-in CI | ✅ (Actions) | ✅ | ✅ (Actions) | ✅ |
| Package registry | ✅ | ✅ | ✅ | ✅ |
| Self-hosted | ✅ | ✅ | ✅ | ❌ |
| Private repos | Unlimited | Unlimited | Unlimited | Unlimited |
| License | MIT | MIT (CE) | GPL | Proprietary |
| Maintenance effort | Low | High | Low | None |
| Monthly cost (self-hosted) | $3.50–$6 | $20+ (needs RAM) | $3.50–$6 | Free |
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
- You’re a solo developer who just needs private repos → GitHub Free gives you unlimited private repos
- You need enterprise SSO, SAML, audit logs out of the box → GitHub Enterprise or GitLab are better fits
- You don’t want to maintain backups or handle security patches → stay on managed platforms
- Your team is > 50 people → the operational overhead probably isn’t worth the savings
What’s Next
Once Gitea is running:
- Mirror your GitHub repos — Settings → Mirror from GitHub. Automatic sync keeps a backup.
- Set up Gitea Actions — Port your GitHub Actions workflows with minimal changes.
- Add package registries — Push npm, Docker, or PyPI packages to your own registry.
- Configure Woodpecker CI — Alternative CI that pairs well with Gitea for complex pipelines.
- Deploy Gitea Pages — Experimental static site hosting from repos (like GitHub Pages).
Summary
| Item | Detail |
|---|---|
| Total monthly cost | $3.50–$6 (VPS only) |
| Setup time | 15–20 minutes |
| Maintenance | ~10 min/week (updates + backup checks) |
| Best budget pick | RackNerd 1 GB @ $3.49/mo |
| Best team pick | Hetzner CX22 @ €3.99/mo |
| Overkill threshold | When your team exceeds 30 people or needs enterprise compliance features |