I deployed Gitea 1.24.4 and Forgejo 15.0.3 LTS on two separate VPS instances, used them for a month, and hit 5 real traps. This article shares those pitfalls and my comparison conclusions so you can skip the pain.
Links to VPS services in this article are affiliate links. If you purchase through them, I may earn a small commission at no extra cost to you.
Gitea or Forgejo? Background First
In late 2022, Gitea announced the formation of Gitea Ltd, a commercial company. The community worried about the project going commercial. Some core contributors forked Forgejo under community governance. Same codebase originally, but diverging since 2023:
| Dimension | Gitea 1.24.4 | Forgejo 15.0.3 LTS |
|---|---|---|
| License | MIT | MIT |
| Governance | Gitea Ltd (company-led) | Community (Codeberg-hosted) |
| CI/CD | Gitea Actions (GitHub Actions compatible) | Forgejo Actions (GitHub Actions compatible) |
| Package Registry | Container/PyPI/NPM etc. | Container/PyPI/NPM etc. |
| Federation | None | ActivityPub (experimental) |
| Min RAM | 512MB usable | 512MB usable |
| Language | Go | Go |
| Binary | Single-file deploy | Single-file deploy |
One-line verdict: Feature-wise they're nearly identical. The decision point is governance — do you trust a company-driven release cadence or a community-driven direction?
Trap 1: SSH Clone Returns Permission Denied
This is the most common and most frustrating issue. You uploaded your SSH public key, but git clone git@your-server:user/repo.git keeps failing:
Permission denied (publickey).
fatal: Could not read from remote repository.
**Root cause**: When deploying with Docker, the container's SSH service (port 22) conflicts with the host SSH. Most people map container SSH to port 2222 but forget to configure ~/.ssh/config:
Host your-server.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
**Another common cause**: The /data/git/.ssh/authorized_keys file inside the container has wrong permissions. If the Docker volume was created by root, the container's git user (UID 1000) can't read it:
# Fix: correct permissions on the host
sudo chown -R 1000:1000 /path/to/gitea/data/git/.ssh
sudo chmod 600 /path/to/gitea/data/git/.ssh/authorized_keys
Verification:
ssh -T git@your-server.com -p 2222
# Success returns: Hi username! You've successfully authenticated...
Trap 2: Docker Networking Causes Reverse Proxy 502
You put Gitea/Forgejo in Docker, with Nginx or Caddy in front for HTTPS termination. The most common problem: Nginx returns 502 Bad Gateway.
**Cause 1**: Nginx and Gitea aren't in the same Docker network. If you deploy Gitea with docker compose, it creates a network named gitea_default by default. Nginx deployed separately or installed system-wide can't reach the Gitea container:
# docker-compose.yml key config
services:
server:
image: gitea/gitea:1.24.4
networks:
- gitnet
nginx:
image: nginx:1.28
networks:
- gitnet # ← Must be on same network
networks:
gitnet:
driver: bridge
**Cause 2**: Nginx proxy_pass uses an IP address instead of the container name. Docker container IPs change on restart:
# ❌ Wrong
proxy_pass http://172.18.0.3:3000;
# ✅ Correct
proxy_pass http://server:3000; # server is the service name in docker-compose
Trap 3: SQLite database is locked Under Concurrent Load
Gitea/Forgejo default to SQLite, which works fine for individuals or small teams. But "fine" has a way of surprising you — like when CI/CD pushes to multiple repos simultaneously:
database is locked (5) (SQLITE_BUSY)
Solution 1: Switch to PostgreSQL. This is the most thorough fix:
services:
db:
image: postgres:17
environment:
POSTGRES_DB: gitea
POSTGRES_USER: gitea
POSTGRES_PASSWORD: your-strong-password
volumes:
- ./postgres:/var/lib/postgresql/data
**Solution 2**: If you insist on SQLite, add WAL mode and timeout in app.ini:
[database]
DB_TYPE = sqlite3
PATH = /data/gitea/gitea.db
SQLITE_JOURNAL_MODE = WAL
SQLITE_BUSY_TIMEOUT = 5000
WAL mode allows concurrent reads and writes, and BUSY_TIMEOUT sets the lock wait to 5 seconds. This resolves most lightweight locking issues.
Trap 4: Backup and Recovery Pitfalls
The core reason to self-host is "data stays with you." But having data doesn't mean you can restore it — I found three traps during testing:
Trap 4a: Only backed up repos, forgot database and LFS
A complete backup should include:
#!/bin/bash
# gitea-backup.sh
GITEA_HOME="/path/to/gitea"
BACKUP_DIR="/backups/gitea/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# 1. Use built-in backup command (Gitea 1.24+)
docker exec -u git server gitea dump -c /data/gitea/conf/app.ini
# 2. Or manually back up critical directories
cp -r "$GITEA_HOME/data/git/repositories" "$BACKUP_DIR/repositories"
cp -r "$GITEA_HOME/data/gitea/lfs" "$BACKUP_DIR/lfs"
cp "$GITEA_HOME/data/gitea/gitea.db" "$BACKUP_DIR/" # SQLite
cp -r "$GITEA_HOME/data/gitea/conf" "$BACKUP_DIR/conf"
Trap 4b: UID/GID mismatch on restore
When restoring on a new server, if the container's git user UID changes, SSH and repository permissions break:
# Verify UID before restore
docker exec server id git
# uid=1000(git) gid=1000(git)
# If mismatched, specify in docker-compose.yml
services:
server:
user: "1000:1000"
Trap 4c: Built-in dump command paths differ in rootless mode
# Root mode
docker exec -u git server gitea dump -c /data/gitea/conf/app.ini
# Rootless mode (note path differences)
docker exec server gitea dump -c /data/gitea/conf/app.ini
# Output file is inside container at /app/gitea/gitea-dump-*.zip
Trap 5: Forgejo Actions Runner Connection Failures
Forgejo Actions are syntax-compatible with GitHub Actions, but the Runner often fails to connect on first deployment:
**Cause 1**: Runner registration used localhost, but Runner is in another container:
# ❌ Runner container can't reach localhost:3000
forgejo-runner register --instance http://localhost:3000
# ✅ Use inter-container communication
forgejo-runner register --instance http://server:3000
**Cause 2**: Forgot to enable Actions in app.ini:
[actions]
ENABLED = true
This configuration is required for both Gitea and Forgejo — it's disabled by default. Many people configure the Runner only to find the server side isn't enabled.
My Final Choice
After a month of deployment, I chose Forgejo 15.0 LTS for these reasons:
1. LTS support until July 2027 — no frequent upgrades needed
2. Community governance gives me more confidence in future direction
3. Feature differences between the two are negligible — neither choice is wrong
But if you prioritize "closest upstream to GitHub," Gitea is the safer bet. Both are mature projects with no wrong answer.
Quick Deploy Reference
Minimal Docker Compose (Forgejo + PostgreSQL):
version: "3"
services:
forgejo:
image: codeberg.org/forgejo/forgejo:15.0
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
volumes:
- ./forgejo:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
depends_on:
- db
db:
image: postgres:17
restart: always
environment:
- POSTGRES_DB=forgejo
- POSTGRES_USER=forgejo
- POSTGRES_PASSWORD=change-me-now
volumes:
- ./postgres:/var/lib/postgresql/data
Deploy command:
docker compose up -d
# Visit http://your-server:3000 to complete initial setup
# Remember to specify port 2222 for SSH cloning
If your VPS has room to spare (2GB+ RAM), consider running OpenClaw alongside for AI agent automation — 👉 Get started: https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link
FAQ
Q: Can I migrate between Gitea and Forgejo?
A: Yes. Their database formats are compatible. To migrate from Gitea to Forgejo, back up Gitea data, then start Forgejo at the same version pointing to the same database. Always verify in a test environment first.
Q: Does a solo developer need Actions?
A: Depends on your automation needs. If you use GitHub Actions for CI/CD, self-hosted Git Actions can migrate workflow files seamlessly. If you just store code and do code reviews, Actions isn't necessary.
Q: Can a 512MB VPS handle it?
A: Barely usable with SQLite and Actions disabled. I recommend at least 1GB. With PostgreSQL, 2GB is more comfortable. Try Vultr's NVMe instances starting at $2.5/mo: https://www.vultr.com/?ref=9890714
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
🔗 Recommended Tools
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: