Forgejo: Self-Hosted Git Hosting Without the GitHub Lock-In
Forgejo: Self-Hosted Git Hosting Without the GitHub Lock-In
Photo by Arno Senoner on Unsplash
If you want to own your code infrastructure without building it yourself, Forgejo is the answer. It's a fully-featured Git hosting platform — pull requests, issues, CI/CD pipelines, container registries, and more — that you can run on a $5 VPS or your homelab server. No per-seat pricing, no vendor lock-in.
Forgejo is a community-controlled fork of Gitea, created in 2022 when Gitea formed a commercial entity. Forgejo commits to staying open-source and community-governed.
What Forgejo Includes
- Git repository hosting with web UI, HTTP/SSH clone
- Pull requests with code review, inline comments, and merge strategies
- Issue tracker with labels, milestones, and projects
- CI/CD via Forgejo Actions (compatible with GitHub Actions syntax)
- Package registry: npm, PyPI, Docker, Maven, NuGet, and more
- Webhooks for integrating with external services
- Organizations and teams with granular permission control
- OIDC/LDAP for enterprise SSO
- Migration tools: import from GitHub, GitLab, Bitbucket
Hardware Requirements
Forgejo is exceptionally lightweight:
- Minimum: 256MB RAM, 1 CPU core
- Recommended: 1GB RAM, 2 CPU cores
- Storage: Depends on repo count — plan for repos + LFS storage
It runs comfortably on a Raspberry Pi 4 or a small VPS.
Docker Compose Setup
# docker-compose.yml
version: "3.8"
services:
forgejo:
image: codeberg.org/forgejo/forgejo:9
container_name: forgejo
restart: unless-stopped
ports:
- "3000:3000" # HTTP
- "22:22" # SSH (or use 2222 and configure SSH client)
volumes:
- forgejo-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
USER_UID: 1000
USER_GID: 1000
FORGEJO__database__DB_TYPE: postgres
FORGEJO__database__HOST: db:5432
FORGEJO__database__NAME: forgejo
FORGEJO__database__USER: forgejo
FORGEJO__database__PASSWD: strongpassword
depends_on:
- db
db:
image: postgres:15
container_name: forgejo-db
restart: unless-stopped
volumes:
- forgejo-pg:/var/lib/postgresql/data
environment:
POSTGRES_DB: forgejo
POSTGRES_USER: forgejo
POSTGRES_PASSWORD: strongpassword
volumes:
forgejo-data:
forgejo-pg:
Start it:
docker compose up -d
Then visit http://your-server:3000 to complete the setup wizard.
Setup Wizard Checklist
- Database: Should auto-populate from env vars — verify it shows PostgreSQL
- Server domain: Set to your actual domain (e.g.,
git.yourdomain.com) - Application URL: Set to
https://git.yourdomain.com - Admin account: Create your admin user here
- Skip optional settings for now — you can configure them later
Like what you're reading? Subscribe to Self-Hosted Weekly — free weekly guides in your inbox.
HTTPS with Caddy
git.yourdomain.com {
reverse_proxy localhost:3000
}
After setting up HTTPS, update your Forgejo config:
# Edit /path/to/forgejo-data/gitea/conf/app.ini
# (or set via environment variable)
FORGEJO__server__ROOT_URL = https://git.yourdomain.com
FORGEJO__server__DOMAIN = git.yourdomain.com
FORGEJO__server__SSH_DOMAIN = git.yourdomain.com
Then restart the container.
SSH Configuration
SSH on port 22 can conflict with your host's SSH daemon. Two options:
Option 1: Move Forgejo SSH to port 2222 (easier):
ports:
- "3000:3000"
- "2222:22"
Users then clone with: git clone ssh://[email protected]:2222/user/repo.git
Option 2: Use SSH passthrough (port 22, no conflict):
Create a git user on the host:
sudo adduser git --disabled-password --shell /bin/bash
Add to your host's ~/.ssh/authorized_keys:
command="ssh -p 22 -o StrictHostKeyChecking=no [email protected] 'SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $SSH_ORIGINAL_COMMAND'" ssh-rsa AAAA...
This is more complex to set up but lets users use the standard port 22.
Forgejo Actions (CI/CD)
Forgejo Actions is compatible with GitHub Actions workflow syntax. Workflows run on self-hosted runners called "act_runner".
Installing the Runner
# On the machine that will run CI jobs
docker run -d \
--name forgejo-runner \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-v forgejo-runner:/data \
code.forgejo.org/forgejo/runner:latest \
forgejo-runner daemon
Register the runner with your Forgejo instance:
docker exec -it forgejo-runner \
forgejo-runner register \
--no-interactive \
--token YOUR_RUNNER_TOKEN \
--name my-runner \
--instance https://git.yourdomain.com \
--labels docker,linux,amd64
Get the runner token from Site Administration → Actions → Runners.
Example Workflow
Create .forgejo/workflows/ci.yml in your repository:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: docker
container: node:20
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
The syntax is nearly identical to GitHub Actions. Most actions/* steps work because Forgejo Actions can pull from GitHub-hosted actions.
Package Registry
Forgejo includes a built-in package registry. Enable it in your app.ini:
[packages]
ENABLED = true
Publishing an npm Package
npm config set registry https://git.yourdomain.com/api/packages/YOUR_ORG/npm/
npm config set //git.yourdomain.com/api/packages/YOUR_ORG/npm/:_authToken YOUR_TOKEN
npm publish
Publishing a Docker Image
docker login git.yourdomain.com
docker tag myimage git.yourdomain.com/youruser/myimage:latest
docker push git.yourdomain.com/youruser/myimage:latest
Migrating from GitHub
Forgejo can mirror entire GitHub repositories or do one-time migrations.
Mirror an existing repo (stays in sync):
- Go to + → New Migration
- Choose GitHub
- Enter your GitHub token and repository URL
- Enable Mirror and set the interval
One-time import:
- Go to + → New Migration
- Choose GitHub
- Uncheck Mirror
- Imports issues, PRs, labels, milestones, and releases
For bulk migration, use the Forgejo API:
# Migrate a repo via API
curl -X POST https://git.yourdomain.com/api/v1/repos/migrate \
-H "Authorization: token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "https://github.com/org/repo",
"auth_token": "GITHUB_TOKEN",
"repo_name": "repo",
"uid": 1,
"mirror": false
}'
OIDC / SSO Integration
Forgejo supports OAuth2 providers. To integrate Authentik:
- In Authentik, create an OAuth2/OpenID provider
- Set redirect URI to
https://git.yourdomain.com/user/oauth2/authentik/callback - In Forgejo: Site Administration → Authentication Sources → Add OAuth2
- Provider: OpenID Connect
- Client ID/Secret from Authentik
- OpenID Connect Auto Discovery URL:
https://auth.yourdomain.com/application/o/forgejo/.well-known/openid-configuration
Users will see a "Sign in with Authentik" button on the login page.
Organization and Team Permissions
For team-based access control:
- Create an Organization for your team/company
- Create Teams with specific permissions (read, write, admin)
- Add Members to teams
- Add Repositories to teams
This lets you give a contractor read-only access to one repo while your full team has write access across all repos.
Backup and Restore
# Backup (stops Forgejo briefly)
docker exec forgejo forgejo admin graceful-restart
docker run --rm \
-v forgejo-data:/data \
-v /backup:/backup \
alpine tar czf /backup/forgejo-$(date +%Y%m%d).tar.gz /data
# Database backup (separate)
docker exec forgejo-db pg_dump -U forgejo forgejo \
> /backup/forgejo-db-$(date +%Y%m%d).sql
Set this up as a daily cron job. Store backups off-site (S3, Backblaze B2, or rsync to another host).
Forgejo vs Gitea vs GitLab
| Feature | Forgejo | Gitea | GitLab CE |
|---|---|---|---|
| Governance | Community | Commercial | Commercial |
| Resource usage | Very light | Very light | Heavy |
| CI/CD | Actions | Actions | GitLab CI |
| Container registry | ✅ | ✅ | ✅ |
| Kubernetes-native | ❌ | ❌ | ✅ (via Helm) |
| Enterprise features | Basic | Basic | Extensive |
Choose Forgejo if you want a lightweight GitHub alternative with community governance. Choose GitLab CE if you need enterprise-grade features and have the hardware to run it (GitLab wants at least 4GB RAM and 4 CPU cores).
Troubleshooting
Push to SSH fails: Run ssh -v [email protected] to debug. Check that the git user's authorized keys are synced from Forgejo (they live in forgejo-data/gitea/.ssh/).
Actions runner not connecting: Check the runner token is correct and that the runner can reach your Forgejo instance on port 443/3000. Firewall issues are the most common cause.
Large repo clone is slow: Enable Git LFS for large files. Forgejo supports LFS natively — configure it in repo settings.
Wrapping Up
Forgejo gives you a GitHub-equivalent experience on hardware you control. The CI/CD system is genuinely capable, the package registry covers the common cases, and the migration tools make it straightforward to pull in repos from GitHub.
For a homelab or small team, Forgejo is hard to beat — it runs on minimal hardware, stays out of the way, and lets you own your entire development workflow.
