Install via docker compose
# docker-compose.yml
services:
forgejo:
image: codeberg.org/forgejo/forgejo:latest
container_name: forgejo
restart: unless-stopped
user: "1000:1000"
ports:
- "127.0.0.1:3000:3000"
- "127.0.0.1:2222:22"
volumes:
- ./data:/var/lib/gitea
- ./config:/etc/gitea
- /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: ${DB_PW}
FORGEJO__server__DOMAIN: git.example.com
FORGEJO__server__ROOT_URL: https://git.example.com
FORGEJO__server__SSH_DOMAIN: git.example.com
FORGEJO__server__SSH_PORT: 2222
depends_on: [ db ]
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: forgejo
POSTGRES_PASSWORD: ${DB_PW}
POSTGRES_DB: forgejo
volumes:
- ./db-data:/var/lib/postgresql/data
Reverse proxy
# Caddy
git.example.com {
reverse_proxy 127.0.0.1:3000
request_body { max_size 500MB }
}
SSH for git: redirect port 22 on a public IP to the container's 2222 via your router / iptables, or front with a TCP-only proxy. Or skip SSH entirely and push over HTTPS with a deploy token.
First-run wizard
Browse to https://git.example.com. The install wizard runs once:
- Database: select Postgres + provide the connection details (from env)
- Server URL: confirm
https://git.example.com - Disable open registration (registration only via admin invite is the safer default)
- Create the admin user
After clicking Install, the wizard is gone forever; subsequent admin happens via the UI or the forgejo CLI inside the container.
What Forgejo gives you
- Repos — Git over HTTPS + SSH, branches, tags, releases, mirroring (pull from / push to other Git remotes)
- Issues + projects — per-repo and org-wide projects with kanban boards, labels, milestones
- Pull requests — with review tooling, CODEOWNERS, branch protection
- Wiki — per-repo wiki (also git-backed)
- Package registry — npm, Maven, NuGet, PyPI, RubyGems, Helm, container (OCI), Composer, Vagrant, Cargo, Conan, Conda. The Forgejo registry replaces "I need a separate Verdaccio + Nexus" for small teams.
- Forgejo Actions — GitHub Actions-compatible CI (runs
act-runneragents). - OAuth provider — other apps can use Forgejo for SSO
- Federation — experimental ActivityPub-based "follow repos / users across instances"
Forgejo Actions: CI built in
Install an act-runner alongside Forgejo:
services:
runner:
image: gitea/act_runner:latest
container_name: forgejo-runner
restart: unless-stopped
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: http://forgejo:3000
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: forgejo-runner-1
volumes:
- ./runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
Get a runner registration token from Forgejo Admin Panel → Actions → Runners. The runner connects, registers itself, picks up jobs.
Drop a .forgejo/workflows/ci.yml in any repo (GitHub Actions syntax):
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npm test
Almost every GitHub Action works unchanged. Some marketplace actions that depend on GitHub API specifically need adjustment, but the common ones (checkout, setup-*, build-push-action) are fine.
OCI registry
# Push container images directly to Forgejo
docker login git.example.com -u amir -p <token>
docker tag myapp git.example.com/myorg/myapp:1.0
docker push git.example.com/myorg/myapp:1.0
# Pull
docker pull git.example.com/myorg/myapp:1.0
One registry for source + container images + Helm charts + other package types. Reduces the "I need Verdaccio for npm + Nexus for Maven + my own Docker registry" sprawl.
Mirroring
Per repo, configure pull mirror (Forgejo pulls from an upstream GitHub repo) or push mirror (Forgejo pushes to a downstream GitHub repo). Useful for:
- Read-only copy of public GitHub repos for "this still works if GitHub is down" patterns
- Bidirectional sync of internal-and-external repos
- Migration from GitHub: pull-mirror, work in Forgejo, eventually flip the canonical
Auth
Beyond the built-in user database:
- OAuth providers: GitHub, GitLab, Discord, etc.
- OIDC: Authentik (see that tutorial), Keycloak, or anything OIDC-compliant
- LDAP / Active Directory
- SMTP-based (login via your mail server's credentials)
For team Forgejo, disable built-in registration + require SSO; new users land via SSO with auto-creation enabled.
Backups
# Built-in dump tool (database + all repos + LFS + attachments)
docker compose exec -u forgejo forgejo forgejo dump -c /etc/gitea/app.ini -f /var/lib/gitea/dump.zip
# Or pg_dump the database + restic the data directory
docker compose exec db pg_dump -U forgejo forgejo > backup-$(date +%F).sql
restic backup ./data
The data directory holds repos as bare Git repos — can be cloned with regular Git tooling if Forgejo is ever broken.
Forgejo vs Gitea vs GitLab vs GitHub Enterprise
- Gitea — the original; Forgejo's parent. After Gitea Ltd's commercial restructuring, the community forked into Forgejo. Both are still maintained; Forgejo is community-governed (Codeberg e.V.).
- GitLab — much bigger; CI built in (GitLab CI); also self-hostable. Significantly heavier resource use.
- GitHub Enterprise Server — commercial; the canonical GitHub experience self-hosted; expensive.
- Bitbucket Server — Atlassian; reaching end-of-life for self-hosted in many tiers.
For "small to medium team that wants a GitHub-shaped forge on hardware they own without GitLab's footprint," Forgejo is the right size in 2026.
When Forgejo isn't enough
- If you need GitHub Marketplace-scale integrations / large team CI minutes / advanced security scanning, GitHub Enterprise has features Forgejo doesn't.
- If you need GitLab-style merge trains, large-team CI workflows, etc., GitLab Self-Managed is the more featureful alternative.
- For "I just want a git server, no UI," plain bare repos over SSH (or Gitolite for fine-grained access) is simpler.