Why Choose Forgejo for Your Self-Hosted Git Forge
Forgejo was born in 2022 as a community fork of Gitea, driven by concerns about the project's governance. Its development is entirely community-led, MIT-licensed, with no dependency on a commercial entity. For teams wanting full control over their source code, CI/CD pipelines, and artifacts without relying on GitHub, GitLab, or Bitbucket, Forgejo is the lightest option available. The interface will feel familiar to any GitHub user, migration from Gitea is seamless, and GitHub Actions compatibility lowers the barrier to adoption.
Concrete Benefits of a Self-Hosted Forgejo
- Full sovereignty: your code never leaves your infrastructure
- GitHub Actions compatible via Forgejo Actions — reuse existing workflows
- Minimal memory footprint (256 MB RAM for a small team)
- Built-in package registry: npm, PyPI, Maven, Helm, OCI container
- No limits on private repositories or collaborators
- ActivityPub federation in progress — future interoperability with other forges
- Open-source, auditable, no pricing surprises
- Controlled updates: you decide when to upgrade
Hardware and Software Requirements with Real Numbers
Size your VPS according to your actual team size before deploying Forgejo. For 1–5 developers with a few dozen repositories, 1 vCPU and 1 GB RAM are sufficient; Forgejo at rest uses around 80 MB. For 5–20 developers with active CI/CD, plan for 2 vCPU and 2 GB RAM. Beyond 20 developers or for large repositories (monorepos, binaries), 4 vCPU and 4 GB RAM ensure a smooth experience. For storage, account for your Git repository volume plus 20% headroom for artifacts and backups.
On the network side, three ports must be open in your firewall: port 80 (HTTP, for Let's Encrypt validation), port 443 (HTTPS, web traffic), and port 2222 (Git SSH, to avoid conflicting with the admin SSH on port 22). On the software side, you need Docker Engine ≥ 24, Docker Compose v2, and a domain name pointing to your VPS.
Deploy Forgejo with Docker and SSL
Prepare the environment
Create the directories that will persist data outside the container:
mkdir -p /opt/forgejo/{data,config,db}
chown -R 1000:1000 /opt/forgejoCreate /opt/forgejo/.env with your values:
FORGEJO_DOMAIN=git.yourdomain.com
FORGEJO_SSH_PORT=2222
POSTGRES_PASSWORD=change_this_passwordCreate the docker-compose.yml
services:
forgejo:
image: codeberg.org/forgejo/forgejo:latest
restart: unless-stopped
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=${POSTGRES_PASSWORD}
volumes:
- /opt/forgejo/data:/data
ports:
- "3000:3000"
- "${FORGEJO_SSH_PORT:-2222}:22"
depends_on:
- db
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_DB=forgejo
- POSTGRES_USER=forgejo
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- /opt/forgejo/db:/var/lib/postgresql/dataStart the services
cd /opt/forgejo
docker compose up -d
docker compose logs -f forgejoWait for the Listen on :3000 line to appear. Forgejo runs its schema migrations automatically on first start.
Configure the reverse proxy and SSL with Caddy
Caddy is the simplest option for automatic Let's Encrypt certificates. Install it and create /etc/caddy/Caddyfile:
git.yourdomain.com {
reverse_proxy localhost:3000
}Start Caddy: systemctl enable --now caddy. The TLS certificate is issued and renewed automatically. If you prefer nginx, configure a standard proxy_pass http://127.0.0.1:3000; block with Certbot.
Complete the instance setup
Open https://git.yourdomain.com in your browser. The setup wizard appears once only. Fill in the domain, the SSH URL (ssh://git.yourdomain.com:2222), the admin email address, and disable public registration on this screen if your forge is for private use. Once the wizard is submitted, the configuration is locked in /opt/forgejo/data/gitea/conf/app.ini.
Install a Forgejo Actions runner
On the same server or a dedicated machine, retrieve the token from *Site Administration → Runners*. Deploy the runner via Docker:
services:
runner:
image: code.forgejo.org/forgejo/act_runner:latest
restart: unless-stopped
environment:
- FORGEJO_INSTANCE_URL=https://git.yourdomain.com
- FORGEJO_RUNNER_TOKEN=your_token
- FORGEJO_RUNNER_NAME=primary-runner
- FORGEJO_RUNNER_LABELS=ubuntu-latest:docker://node:20,docker:docker://docker:dind
volumes:
- /var/run/docker.sock:/var/run/docker.sockThe runner appears in the interface within a minute. It can execute jobs in Docker mode (full isolation, recommended), Process mode (without Docker, for lightweight shell tasks), or Auto (detects based on the workflow label).
Writing Your First Forgejo Actions Workflows
Compatibility with GitHub Actions syntax is broad. Create .forgejo/workflows/ci.yml at the root of your repository:
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm ci
npm testThe runs-on labels correspond to the labels declared when registering the runner. You can define multiple runners with different labels (e.g., arm64, gpu, high-memory) and target the right environment per job. Artifacts and caching work with the same official actions as on GitHub.
Post-Install Security: Essential Settings
A freshly installed Forgejo is functional but not hardened. Here are the six points to address before opening the forge to your team.
Disable public registration. In *Site Administration → Settings → Users*, uncheck "Allow user registration". On an internal forge, nobody should be able to create an account without an invitation.
Enable 2FA for administrators. Every admin account must enable two-factor authentication under *Settings → Security*. Enforce 2FA for all users via the REQUIRE_SIGNIN_VIEW setting and organizational security policies.
SSH keys only. Disable password authentication in the host server's /etc/ssh/sshd_config (PasswordAuthentication no). For Git SSH (port 2222), Forgejo only accepts public keys registered in user profiles — this is its default behavior.
Webhook secrets. When creating each webhook (to external CI, Slack, etc.), always fill in the *Secret* field. Forgejo signs the payload with HMAC-SHA256; your receiver must verify this signature before processing the event.
Private packages. If you use the built-in package registry, set the default visibility to *Private* in organization settings. Access tokens for publishing should have minimal scope (packages:write only, never a global admin token).
Firewall. Close all ports except 80, 443, and 2222. Port 3000 (internal Forgejo) must never be exposed directly; it is consumed only by the reverse proxy on loopback.
Updating Forgejo Without Downtime
The Docker update procedure is straightforward and requires only a few seconds of unavailability.
cd /opt/forgejo
# 1. Pull the new image
docker compose pull forgejo
# 2. Back up the database before any migration
docker compose exec db pg_dump -U forgejo forgejo > backup-$(date +%Y%m%d).sql
# 3. Restart the service
docker compose up -d forgejo
# 4. Verify schema migrations
docker compose logs forgejo | grep -i migratForgejo runs its migrations automatically on startup. Verify that a Finished successfully line appears in the logs. In case of a migration error, restore the SQL backup and report the issue on the Forgejo tracker. Always read the release notes before jumping major versions — major version migrations may require an intermediate step.
Migrate from Gitea in Five Minutes
Forgejo is a direct fork of Gitea: its database schema is compatible up to version 1.21. To migrate, stop Gitea, copy its data directory (/data or /opt/gitea) to /opt/forgejo/data, replace the image in your docker-compose, and restart. Forgejo automatically detects the Gitea schema and applies its own migrations. Your repositories, users, SSH keys, webhooks, and issues are fully preserved. For newer Gitea versions, check the compatibility matrix on the Forgejo wiki before migrating.
Troubleshooting: Most Common Errors
SSH connection refused on port 2222. First verify the port is published by Docker (docker compose ps → Ports column). Then test from the client: ssh -p 2222 [email protected]. If the response is PTY allocation request failed, the connection is working — Forgejo responds Hi <user>! You've successfully authenticated. If you get Connection refused, the firewall is blocking the port: ufw allow 2222/tcp.
Runner showing offline in the interface. The runner contacts Forgejo over outbound HTTPS. Verify the runner container can resolve and reach git.yourdomain.com. A self-signed certificate requires mounting your CA in the container. Restart the runner after fixing connectivity: docker compose restart runner.
Push rejected by a pre-receive hook. Forgejo may reject a push if a branch protection rule is active (code review required, mandatory CI tests) or if a file size rule is exceeded. The git error message contains the details. For large binaries (> 100 MB), use Git LFS: Forgejo supports the LFS protocol natively.
Slow interface after several months. Run the Git garbage collector on active repositories from the admin panel: *Administration → Repositories → Git Repositories → Run Git GC*. To automate this, configure the scheduled task in app.ini under [cron.run_task].
ActivityPub Federation: What Forgejo Is Building
Forgejo is the first Git forge project to implement federation via the ActivityPub protocol (the same one used by Mastodon). As of version 1.20+, you can already follow a remote repository hosted on another federated Forgejo instance and receive issue and pull request notifications in your local timeline. Full federation — cross-instance forking, inter-forge pull requests — is being standardized within the ForgeFed working group. If your collaborators are on different forges, this evolution will eventually allow contributing without creating an account on each instance.
Forgejo vs Gitea vs GitLab CE: Choosing Your Forge
| Criterion | Forgejo | Gitea | GitLab CE |
|---|---|---|---|
| Governance | Community (Codeberg e.V.) | Commercial (Gitea Ltd) | Commercial (GitLab Inc) |
| Minimum RAM | ~80 MB | ~80 MB | ~4 GB |
| GitHub Actions compatibility | Yes (Forgejo Actions) | Yes (Gitea Act) | No (proprietary CI) |
| Package registry | Yes (multi-format) | Yes | Yes |
| ActivityPub federation | In progress (ForgeFed) | No | No |
| Migration from GitHub | Yes (mirror + import) | Yes | Yes |
| Admin interface | Simple, built-in | Simple, built-in | Full-featured but heavy |
| License | MIT | MIT | MIT (CE) / EE (paid) |
| Release frequency | Monthly | Monthly | Monthly |