Skip to main content

How to Self-Host Forgejo: Community Git Forge 2026

·OSSAlt Team
forgejogitgithubself-hostingdockerdevopsfederation2026

TL;DR

Forgejo (GPL 3.0, ~11K GitHub stars, Go) is the community-driven fork of Gitea — created in 2022 when Gitea's core team moved to a for-profit model. Forgejo is 100% volunteer-run and a Codeberg project. It's functionally near-identical to Gitea (same Docker image, same API, same YAML CI syntax) but with a stronger governance model, more active federation work, and stricter open-source principles. If you want Gitea's features but prefer a community project with no corporate backing, Forgejo is the choice.

Key Takeaways

  • Forgejo: GPL 3.0, ~11K stars — Gitea fork with community governance and no corporate backing
  • Drop-in Gitea replacement: Same Docker config, same API, same CI YAML — migrates in minutes
  • Forgejo Actions: GitHub Actions-compatible CI/CD (same as Gitea Actions)
  • Federation: Actively working on ActivityPub federation (F3 protocol) for cross-forge PRs
  • Codeberg hosted: codeberg.org runs Forgejo — the largest public instance
  • Migration: Migrate from Gitea by just switching the Docker image tag

Forgejo vs Gitea

AspectForgejoGitea
LicenseGPL 3.0MIT
GovernanceNonprofit/communityGitea Inc. (for-profit)
GitHub Stars~11K~43K
Gitea compatibilityNear 100%
CI/CDForgejo Actions (GH Actions compatible)Gitea Actions (GH Actions compatible)
ActivityPub federationActive developmentExperimental
Public instancecodeberg.orggitea.com
Release cadenceMore frequent patchesQuarterly

Part 1: Docker Setup

Forgejo uses the same configuration structure as Gitea — just swap the image:

# docker-compose.yml
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:latest
    container_name: forgejo
    restart: unless-stopped
    ports:
      - "3000:3000"
      - "2222:22"
    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: postgres:5432
      FORGEJO__database__NAME: forgejo
      FORGEJO__database__USER: forgejo
      FORGEJO__database__PASSWD: "${POSTGRES_PASSWORD}"
      FORGEJO__server__DOMAIN: "git.yourdomain.com"
      FORGEJO__server__ROOT_URL: "https://git.yourdomain.com"
      FORGEJO__server__SSH_DOMAIN: "git.yourdomain.com"
      FORGEJO__server__SSH_PORT: 2222
      FORGEJO__server__HTTP_PORT: 3000
      FORGEJO__service__DISABLE_REGISTRATION: "false"   # Enable for initial setup
      FORGEJO__federation__ENABLED: "true"              # Enable ActivityPub federation
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: forgejo
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: forgejo
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U forgejo"]
      interval: 10s
      start_period: 20s

volumes:
  forgejo_data:
  postgres_data:
# .env
POSTGRES_PASSWORD=your-secure-db-password

docker compose up -d

Visit http://your-server:3000 → complete the installation wizard.


Part 2: HTTPS with Caddy

git.yourdomain.com {
    reverse_proxy localhost:3000
}

Part 3: Migrate from Gitea

If you're already running Gitea, migration is a single image swap:

# 1. Stop Gitea:
docker compose stop gitea

# 2. Backup data:
tar -czf gitea-backup-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect gitea_gitea_data --format '{{.Mountpoint}}')

# 3. Update docker-compose.yml:
# Change: image: gitea/gitea:latest
# To:     image: codeberg.org/forgejo/forgejo:latest
# Change: GITEA__* env vars to FORGEJO__*

# 4. Start Forgejo with existing data volume:
docker compose up -d

# Forgejo auto-migrates Gitea database schema

Env var prefix change: Gitea uses GITEA__ prefix, Forgejo uses FORGEJO__ prefix. All other settings are identical.


Part 4: Forgejo Actions (CI/CD)

Enable Forgejo Actions (GitHub Actions-compatible):

environment:
  FORGEJO__actions__ENABLED: "true"

Run a Forgejo Runner

# Add to docker-compose.yml:
services:
  forgejo-runner:
    image: code.forgejo.org/forgejo/runner:latest
    container_name: forgejo_runner
    restart: unless-stopped
    volumes:
      - ./runner-config.yaml:/config.yaml
      - forgejo_runner_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      CONFIG_FILE: /config.yaml
      FORGEJO_INSTANCE_URL: "https://git.yourdomain.com"
      FORGEJO_RUNNER_REGISTRATION_TOKEN: "${RUNNER_TOKEN}"
      FORGEJO_RUNNER_NAME: "docker-runner"
# Register runner (get token from Site Administration → Runners):
docker exec forgejo_runner forgejo-runner register \
  --instance https://git.yourdomain.com \
  --token YOUR_RUNNER_TOKEN \
  --name docker-runner \
  --labels ubuntu-latest:docker://node:20-bullseye

Workflow example (GitHub Actions compatible)

# .forgejo/workflows/ci.yml
# Identical syntax to GitHub Actions / Gitea Actions
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: |
          npm ci
          npm test
          npm run build

  release:
    if: github.ref == 'refs/heads/main'
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create release
        uses: https://codeberg.org/forgejo/forgejo-action-release@v1
        with:
          direction: release

Part 5: ActivityPub Federation

Forgejo is building F3 (Federated Forge Format) — cross-forge pull requests and issues:

environment:
  FORGEJO__federation__ENABLED: "true"

What federation enables (current state):

  • Your Forgejo instance is discoverable via ActivityPub
  • Users on other Forgejo/Gitea instances can be @mentioned cross-instance
  • Cross-forge stars (experimental)

Roadmap: Full cross-forge pull requests (PR from alice@other-forge.com to a repo on your instance) — the ForgeFed protocol specification.

# Verify federation is working:
curl https://git.yourdomain.com/.well-known/webfinger?resource=acct:admin@git.yourdomain.com
# Returns: ActivityPub WebFinger response

Part 6: Packages and Registries

Forgejo includes multiple package registries:

# Container registry:
docker login git.yourdomain.com
docker push git.yourdomain.com/username/myimage:latest

# npm registry:
npm config set @myorg:registry https://git.yourdomain.com/api/packages/username/npm/
npm login --scope=@myorg --registry=https://git.yourdomain.com/api/packages/username/npm/
npm publish --scope=@myorg

# PyPI:
pip install --index-url https://username:token@git.yourdomain.com/api/packages/username/pypi/simple/ mypackage

# Maven (in pom.xml):
# <repository>
#   <id>forgejo</id>
#   <url>https://git.yourdomain.com/api/packages/username/maven</url>
# </repository>

Part 7: Migrate from GitHub

# Single repo via UI:
# + → New Migration → GitHub → enter URL + token

# Bulk migration script (same as Gitea):
#!/bin/bash
GITHUB_TOKEN="ghp_yourtoken"
FORGEJO_URL="https://git.yourdomain.com"
FORGEJO_TOKEN="your-forgejo-token"

REPOS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/user/repos?per_page=100" | jq -r '.[].full_name')

for REPO in $REPOS; do
  curl -X POST "${FORGEJO_URL}/api/v1/repos/migrate" \
    -H "Authorization: token ${FORGEJO_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{
      \"clone_addr\": \"https://github.com/${REPO}\",
      \"auth_token\": \"${GITHUB_TOKEN}\",
      \"repo_name\": \"$(echo $REPO | cut -d/ -f2)\",
      \"mirror\": false,
      \"issues\": true,
      \"pull_requests\": true,
      \"releases\": true
    }"
done

Part 8: OAuth2 / SSO

Forgejo supports multiple OAuth2 providers:

# Via app.ini or env vars:
FORGEJO__oauth2__ENABLED: "true"

# Add provider via admin UI:
# Site Administration → Authentication Sources → Add Authentication Source
# → OAuth2 → GitHub / GitLab / Gitea / Keycloak / custom OIDC
# Example: Authentik as OIDC provider
environment:
  FORGEJO__oauth2__ENABLED: "true"

In Authentik:

  1. Applications → Providers → Create → OAuth2/OpenID Provider
  2. Redirect URI: https://git.yourdomain.com/user/oauth2/authentik/callback
  3. Copy Client ID + Secret to Forgejo OAuth2 settings

Maintenance

# Update Forgejo:
docker compose pull
docker compose up -d

# Backup:
docker compose stop forgejo
tar -czf forgejo-backup-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect forgejo_forgejo_data --format '{{.Mountpoint}}')
docker exec forgejo-postgres-1 pg_dump -U forgejo forgejo \
  | gzip > forgejo-db-$(date +%Y%m%d).sql.gz
docker compose start forgejo

# Logs:
docker compose logs -f forgejo

# Admin CLI:
docker exec -u git forgejo forgejo admin user create \
  --username alice --email alice@yourdomain.com --password secret --admin

See all open source DevOps and Git hosting tools at OSSAlt.com/categories/devops.

Comments