<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/gitea-vs-forgejo-vs-gogs-self-hosted-git-platforms-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/gitea-vs-forgejo-vs-gogs-self-hosted-git-platforms-2026/raw.md -->
<!-- Source path: content/guides/gitea-vs-forgejo-vs-gogs-self-hosted-git-platforms-2026.mdx -->

---
og_image: "/images/guides/gitea-vs-forgejo-vs-gogs-self-hosted-git-platforms-2026.webp"
title: "Gitea vs Forgejo vs Gogs: Self-Hosted Git 2026"
description: "Gitea, Forgejo, and Gogs compared for self-hosted Git in 2026. CI/CD, package registries, federation, resource usage, and which Git platform to choose."
date: "2026-03-09"
author: "PkgPulse Team"
tags: ["javascript", "typescript", "devops", "git"]
tier: 2
---

## TL;DR

**Gitea** is the safest default for self-hosted Git in 2026 when you want a GitHub-like product with Actions, packages, projects, reviews, and active upstream releases. **Forgejo** is the community-governed Gitea soft fork: choose it when governance, Codeberg alignment, and privacy direction matter more than staying closest to Gitea Ltd's upstream; treat its ActivityPub federation work as an experimental opt-in, not a production default. **Gogs** remains the smallest, simplest Git server for teams that only need repositories, issues, pull requests, SSH/HTTP Git, and a low-maintenance single-binary deployment. The decision is less "which one can host Git" and more "how much forge, CI, registry, and governance surface do you want to operate?"

## Key Takeaways

- **Gitea**: about 55.7K GitHub stars on 2026-05-16; Actions, packages, projects, reviews, organizations, mirrors, and admin tooling.
- **Forgejo**: about 4.6K Codeberg stars on 2026-05-16; community governance, Forgejo Actions, packages, and a project focus on security, scaling, federation experiments, and privacy.
- **Gogs**: about 47.5K GitHub stars on 2026-05-16; minimal Git hosting with fewer built-in DevOps features.
- Gitea and Forgejo both cover the "GitHub-like self-hosted forge" use case; Gogs is intentionally smaller.
- Treat exact RAM and benchmark numbers as deployment-dependent: database choice, repository count, LFS, search, and CI runners matter more than marketing-size claims.
- All `*.example.com`, placeholder passwords, and tokens in the snippets below are illustrative code-sample values, not external source URLs or availability evidence.

---

## Gitea

[Gitea](https://gitea.io) — lightweight self-hosted Git:

### Installation

```bash
# Docker:
docker run -d \
  --name gitea \
  -p 3000:3000 \
  -p 2222:22 \
  -v gitea-data:/data \
  -v /etc/timezone:/etc/timezone:ro \
  gitea/gitea:latest

# Docker Compose:
```

```yaml
# docker-compose.yml
version: "3.8"

services:
  gitea:
    image: gitea/gitea:latest
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=secret
      - GITEA__server__ROOT_URL=https://git.example.com
      - GITEA__server__SSH_DOMAIN=git.example.com
      - GITEA__server__SSH_PORT=2222
    volumes:
      - gitea-data:/data
      - /etc/timezone:/etc/timezone:ro
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: gitea
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  gitea-data:
  postgres-data:
```

### API usage

```typescript
// Gitea REST API:
const GITEA_URL = "https://git.example.com"
const GITEA_TOKEN = process.env.GITEA_TOKEN!

const headers = {
  Authorization: `token ${GITEA_TOKEN}`,
  "Content-Type": "application/json",
}

// List repositories:
const repos = await fetch(`${GITEA_URL}/api/v1/repos/search?limit=20`, {
  headers,
}).then((r) => r.json())

// Create repository:
const newRepo = await fetch(`${GITEA_URL}/api/v1/user/repos`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "my-project",
    description: "A new project",
    private: false,
    auto_init: true,
    default_branch: "main",
    license: "MIT",
    gitignores: "Node",
  }),
}).then((r) => r.json())

// Create issue:
const issue = await fetch(
  `${GITEA_URL}/api/v1/repos/myorg/my-project/issues`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      title: "Fix package detection",
      body: "The package scanner misses TypeScript packages.",
      labels: [1, 3],
      assignees: ["developer1"],
    }),
  }
).then((r) => r.json())

// Create pull request:
const pr = await fetch(
  `${GITEA_URL}/api/v1/repos/myorg/my-project/pulls`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      title: "Add package scanner improvements",
      body: "Fixes #42\n\nAdds TypeScript package detection.",
      head: "feature/ts-detection",
      base: "main",
      assignees: ["reviewer1"],
      labels: [2],
    }),
  }
).then((r) => r.json())

// Merge pull request:
await fetch(
  `${GITEA_URL}/api/v1/repos/myorg/my-project/pulls/${pr.number}/merge`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      Do: "squash",
      merge_message_field: "Add package scanner improvements (#43)",
      delete_branch_after_merge: true,
    }),
  }
)

// Create webhook:
await fetch(`${GITEA_URL}/api/v1/repos/myorg/my-project/hooks`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    type: "gitea",
    config: {
      url: "https://api.example.com/webhooks/gitea",
      content_type: "json",
      secret: "webhook-secret",
    },
    events: ["push", "pull_request", "issues"],
    active: true,
  }),
})
```

### Gitea Actions (CI/CD)

```yaml
# .gitea/workflows/ci.yml — GitHub Actions compatible!
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Build and push Docker image
        run: |
          docker build -t registry.example.com/my-project:${{ github.sha }} .
          docker push registry.example.com/my-project:${{ github.sha }}

      - name: Deploy
        run: |
          curl -X POST "https://deploy.example.com/api/deploy" \
            -H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}" \
            -d '{"image": "registry.example.com/my-project:${{ github.sha }}"}'
```

### Package registry

```bash
# Gitea has a built-in package registry:

# npm packages:
npm config set @myorg:registry https://git.example.com/api/packages/myorg/npm/
npm config set //git.example.com/api/packages/myorg/npm/:_authToken ${GITEA_TOKEN}
npm publish

# Docker images:
docker login git.example.com
docker build -t git.example.com/myorg/my-project:latest .
docker push git.example.com/myorg/my-project:latest

# Supported formats: npm, PyPI, Maven, NuGet, Cargo, Container, Helm, etc.
```

---

## Forgejo

[Forgejo](https://forgejo.org) — community-driven Git forge:

### Installation

```bash
# Docker:
docker run -d \
  --name forgejo \
  -p 3000:3000 \
  -p 2222:22 \
  -v forgejo-data:/data \
  codeberg.org/forgejo/forgejo:latest
```

```yaml
# docker-compose.yml
version: "3.8"

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:latest
    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=secret
      - FORGEJO__server__ROOT_URL=https://forge.example.com
    volumes:
      - forgejo-data:/data
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: forgejo
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: forgejo
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  forgejo-data:
  postgres-data:
```

### API (Gitea-compatible)

```typescript
// Forgejo API — compatible with Gitea API:
const FORGEJO_URL = "https://forge.example.com"
const FORGEJO_TOKEN = process.env.FORGEJO_TOKEN!

const headers = {
  Authorization: `token ${FORGEJO_TOKEN}`,
  "Content-Type": "application/json",
}

// All Gitea API endpoints work:
// List repos:
const repos = await fetch(`${FORGEJO_URL}/api/v1/repos/search`, {
  headers,
}).then((r) => r.json())

// Create repo:
const repo = await fetch(`${FORGEJO_URL}/api/v1/user/repos`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "my-project",
    description: "Built on Forgejo",
    private: false,
    auto_init: true,
    default_branch: "main",
  }),
}).then((r) => r.json())

// Create organization:
const org = await fetch(`${FORGEJO_URL}/api/v1/orgs`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    username: "myteam",
    full_name: "My Team",
    description: "Development team",
    visibility: "public",
  }),
}).then((r) => r.json())

// Manage teams:
await fetch(`${FORGEJO_URL}/api/v1/orgs/myteam/teams`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "developers",
    permission: "write",
    units: ["repo.code", "repo.issues", "repo.pulls"],
  }),
})
```

### Federation (ActivityPub)

Forgejo's federation setting is not something to turn on casually in a default Docker Compose file. The official Forgejo configuration cheat sheet currently marks federation as "under active development" and "experimental," leaves `ENABLED` at `false` by default, and warns that enabling it affects whether the same domain can federate with other software later. If federation is part of your decision, test it on a staging instance or dedicated subdomain and re-check the current Forgejo docs before enabling it on a production forge.

```typescript
// Forgejo federation — only after explicit opt-in and current-docs review:

// Star a repository on another Forgejo instance:
// Users can follow repos on remote instances via ActivityPub
// Example: user@forge-a.example.com can star repo@forge-b.example.com

// The federation API exposes ActivityPub endpoints:
// GET /api/v1/activitypub/user-id/{userId}
// GET /api/v1/activitypub/repository-id/{repoId}

// Check federation status:
const federationInfo = await fetch(
  `${FORGEJO_URL}/api/v1/nodeinfo`,
  { headers }
).then((r) => r.json())

// Federation enables:
// - Cross-instance repository starring
// - Cross-instance user following
// - Federated pull requests (in development)
// - Distributed issue tracking (planned)
```

### Forgejo Actions (CI/CD)

```yaml
# .forgejo/workflows/ci.yml — same as Gitea Actions
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: docker
    container:
      image: node:20-alpine
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
      - run: npm run build
```

```bash
# Setup Forgejo Runner:
docker run -d \
  --name forgejo-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e FORGEJO_URL=https://forge.example.com \
  -e FORGEJO_TOKEN=runner-registration-token \
  codeberg.org/forgejo/runner:latest
```

---

## Gogs

[Gogs](https://gogs.io) — painless self-hosted Git:

### Installation

```bash
# Docker (simplest):
docker run -d \
  --name gogs \
  -p 3000:3000 \
  -p 2222:22 \
  -v gogs-data:/data \
  gogs/gogs:latest

# Binary (single file):
wget https://dl.gogs.io/0.13.0/gogs_0.13.0_linux_amd64.tar.gz
tar -xzf gogs_0.13.0_linux_amd64.tar.gz
cd gogs
./gogs web
```

```yaml
# docker-compose.yml — minimal setup
version: "3.8"

services:
  gogs:
    image: gogs/gogs:latest
    ports:
      - "3000:3000"
      - "2222:22"
    volumes:
      - gogs-data:/data
    environment:
      - RUN_CROND=true

  # Gogs works with SQLite (no external DB needed):
  # Or use PostgreSQL/MySQL for production:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: gogs
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: gogs
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  gogs-data:
  postgres-data:
```

### API usage

```typescript
// Gogs REST API:
const GOGS_URL = "https://git.example.com"
const GOGS_TOKEN = process.env.GOGS_TOKEN!

const headers = {
  Authorization: `token ${GOGS_TOKEN}`,
  "Content-Type": "application/json",
}

// List user's repos:
const repos = await fetch(`${GOGS_URL}/api/v1/user/repos`, {
  headers,
}).then((r) => r.json())

// Create repository:
const repo = await fetch(`${GOGS_URL}/api/v1/user/repos`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "my-project",
    description: "A simple project",
    private: false,
    auto_init: true,
  }),
}).then((r) => r.json())

// Create issue:
const issue = await fetch(
  `${GOGS_URL}/api/v1/repos/myuser/my-project/issues`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      title: "Bug report",
      body: "Found a bug in the parser",
    }),
  }
).then((r) => r.json())

// Add collaborator:
await fetch(
  `${GOGS_URL}/api/v1/repos/myuser/my-project/collaborators/otheruser`,
  {
    method: "PUT",
    headers,
    body: JSON.stringify({
      permission: "write",
    }),
  }
)

// Create webhook:
await fetch(`${GOGS_URL}/api/v1/repos/myuser/my-project/hooks`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    type: "gogs",
    config: {
      url: "https://api.example.com/webhooks/gogs",
      content_type: "json",
      secret: "webhook-secret",
    },
    events: ["push", "create", "pull_request"],
    active: true,
  }),
})
```

### Configuration

```ini
; custom/conf/app.ini — Gogs configuration
[server]
DOMAIN           = git.example.com
HTTP_PORT        = 3000
ROOT_URL         = https://git.example.com/
DISABLE_SSH      = false
SSH_PORT         = 2222
OFFLINE_MODE     = false

[database]
DB_TYPE  = postgres
HOST     = db:5432
NAME     = gogs
USER     = gogs
PASSWD   = secret
SSL_MODE = disable

[repository]
ROOT            = /data/git/repositories
DEFAULT_BRANCH  = main

[security]
INSTALL_LOCK = true
SECRET_KEY   = your-secret-key

[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL     = false
DISABLE_REGISTRATION   = false
REQUIRE_SIGNIN_VIEW    = false

[mailer]
ENABLED = false

[log]
MODE      = console
LEVEL     = Info
ROOT_PATH = /app/gogs/log
```

### Webhook handler

```typescript
// Handle Gogs webhooks in your app:
import express from "express"
import crypto from "crypto"

const app = express()
app.use(express.json())

function verifyGogsSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex")
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

app.post("/webhooks/gogs", (req, res) => {
  const signature = req.headers["x-gogs-signature"] as string
  const event = req.headers["x-gogs-event"] as string

  if (!verifyGogsSignature(JSON.stringify(req.body), signature, "webhook-secret")) {
    return res.status(401).json({ error: "Invalid signature" })
  }

  switch (event) {
    case "push":
      const { ref, commits, repository } = req.body
      console.log(`Push to ${repository.full_name}:${ref}`)
      commits.forEach((c: any) => console.log(`  ${c.id.slice(0, 7)} ${c.message}`))
      break

    case "pull_request":
      const { action, pull_request } = req.body
      console.log(`PR ${action}: ${pull_request.title}`)
      break

    case "issues":
      console.log(`Issue ${req.body.action}: ${req.body.issue.title}`)
      break
  }

  res.json({ received: true })
})

app.listen(4000)
```

---

## Feature Comparison

| Feature | Gitea | Forgejo | Gogs |
|---------|-------|---------|------|
| Language | Go | Go | Go |
| Origin | Gogs fork (2016) | Gitea fork (2022) | Original (2014) |
| Built-in CI/CD | ✅ (Gitea Actions) | ✅ (Forgejo Actions) | ❌ |
| GitHub Actions compat | ✅ | ✅ | ❌ |
| Package registry | ✅ (npm, Docker, PyPI, etc.) | ✅ | ❌ |
| Projects/Kanban | ✅ | ✅ | ❌ |
| Federation | ❌ | Experimental ActivityPub; disabled by default | ❌ |
| Organizations | ✅ | ✅ | ✅ |
| Pull requests | ✅ | ✅ | ✅ |
| Code review | ✅ | ✅ | Basic |
| Wiki | ✅ | ✅ | ✅ |
| Issue tracker | ✅ | ✅ | ✅ |
| Webhooks | ✅ | ✅ | ✅ |
| OAuth2 provider | ✅ | ✅ | ❌ |
| LDAP/AD | ✅ | ✅ | ✅ |
| Mirror repos | ✅ | ✅ | ✅ |
| LFS support | ✅ | ✅ | ✅ |
| Resource usage | Generally low; depends on DB, repos, LFS, and runners | Generally low; similar operating profile to Gitea | Smallest surface, but still depends on DB/repos |
| SQLite support | ✅ | ✅ | ✅ |
| Governance | Gitea Ltd stewarded open-source project | Community-governed soft fork aligned with Codeberg | Original maintainer-led project |

---

## When to Use Each

**Use Gitea if:**
- Want the most feature-rich self-hosted Git with built-in CI/CD
- Need GitHub Actions-compatible workflows on your own infrastructure
- Want a built-in package registry (npm, Docker, PyPI, etc.)
- Building a complete DevOps platform with projects and kanban boards

**Use Forgejo if:**
- Want community-governed open-source Git hosting
- Want to evaluate experimental ActivityPub federation for cross-instance collaboration on a staged or dedicated-domain deployment
- Prefer the same features as Gitea with stronger governance guarantees
- Supporting the decentralized, federated software forge movement

**Use Gogs if:**
- Want the simplest, most lightweight self-hosted Git server
- Need a single binary with the smallest operating surface of the three
- Only need basic Git hosting (repos, issues, PRs, webhooks)
- Running on constrained hardware (Raspberry Pi, small VPS)

---

## Administration and Day-Two Operations

Feature selection at install time is only half the decision. The difference in operational burden between Gitea, Forgejo, and Gogs becomes clear when something goes wrong or when the team grows.

Gitea's administrative panel is the most comprehensive of the three. You can manage users, organizations, OAuth apps, SSH keys, banned email patterns, and running cron jobs from the web UI without touching the filesystem. Upgrade paths are well-documented and typically require only a container image swap followed by an automatic database migration on first boot. The admin API is complete enough that most operations can be scripted — user provisioning, repository creation, and team assignment all have REST endpoints. Gitea also ships with a built-in git repository health checker and automatic mirror synchronization, which reduces the manual work of keeping forks in sync with upstream repositories.

Forgejo's day-two operations are intentionally close to Gitea for core repository, user, organization, and package workflows. The practical operational difference is governance and release policy rather than a radically different admin UI. If your team values community ownership, Codeberg compatibility, privacy direction, and a project willing to experiment with federation, Forgejo gives you those priorities without forcing a move to an unfamiliar product model.

Gogs provides the least administrative surface, which is a feature rather than a limitation. If your only requirement is "run a small Git server with fewer moving parts," Gogs can fit constrained VPS or homelab deployments when repository count, traffic, search, LFS, and backup needs stay modest. The admin panel handles user management and repository visibility; it does not have background job management, health checks, or a complete admin API. When something does need attention — upgrading, migrating to a larger server, enabling email — the Gogs documentation and community are smaller and less current than Gitea's.

---

## Migrating Between Platforms

When evaluating self-hosted Git options, understanding the migration path in both directions matters — especially if you're starting with the simpler option and expect to need more features later.

Migrating from Gogs to Gitea is a common path, but it should be treated as a production migration rather than a casual binary swap. Back up the database and repository storage, test the migration on a copy, verify hooks/LFS/SSH paths, then cut over. The repository data may not need to move when the layout is compatible, but the database and configuration still deserve a staged migration plan.

Migrating from Gitea to Forgejo can be straightforward because Forgejo is a soft fork, but the official migration guidance still treats it as an upgrade project: confirm your current Gitea version is on a supported path, shut down the source service, take database and file/repository backups, test the migration on a copy, then cut over. Forgejo may run automatic database migrations as part of startup, but do not assume the image swap alone proves every repository, hook, LFS object, SSH path, user, and permission survived; verify those on a staging instance before moving production traffic. Federation remains an explicit post-migration configuration choice, not something the migration should silently enable.

Migrating outward to GitHub or GitLab from any of these platforms is less smooth. Gitea has a GitHub importer and can push mirror repositories to GitHub, but issue history, pull request data, and wiki pages require custom export scripts. For teams considering a future migration to GitHub Enterprise or GitLab, keeping your self-hosted Git as a mirror of a GitHub/GitLab source of truth — rather than the source of record — is the easier long-term strategy.

---

## Security Model and Authentication

All three platforms support SSH key management, HTTPS access, and LDAP/Active Directory authentication out of the box. The differences appear in more advanced authentication scenarios and in how each platform handles the security of the CI/CD layer.

Gitea and Forgejo both support OAuth2 as a provider — meaning you can use Gitea or Forgejo as the OAuth server for other internal tools. This matters for teams building a self-hosted DevOps platform: you can authenticate your package registry, internal documentation, and CI dashboard against the same Gitea OAuth server without running a separate identity provider. SAML support requires a reverse proxy (Authelia, Keycloak) for all three platforms; none implements SAML natively.

Gitea Actions secrets management is worth highlighting specifically. When you use Gitea Actions for CI/CD, secret values (API keys, deployment credentials) are stored encrypted in the Gitea database and injected as environment variables into runner jobs. This is a complete secrets management system that Gogs completely lacks — with Gogs, secrets for CI pipelines must be managed externally. For teams that need CI/CD without building a separate secrets management layer, this is a decisive capability gap.

Gogs provides only basic token authentication and does not function as an OAuth provider. For organizations with strict security requirements — signed commits, commit signature verification in pull requests, or secrets injection for CI workflows — Gogs does not have these capabilities and should not be chosen when they are requirements. Teams that start with Gogs and later need these features typically migrate to Gitea, which is why understanding the migration path described above is worth doing before the initial deployment rather than after.

---

## Sources checked

- [Gitea official Docker installation docs](https://docs.gitea.com/installation/install-with-docker) — container deployment model and configuration surface; accessed 2026-05-16.
- [Gitea Actions overview](https://docs.gitea.com/usage/actions/overview) — CI/CD capabilities and runner model; accessed 2026-05-16.
- [Gitea packages overview](https://docs.gitea.com/usage/packages/overview) — built-in package registry formats; accessed 2026-05-16.
- [Gitea GitHub repository](https://github.com/go-gitea/gitea) — current star count, activity, and release posture; accessed 2026-05-16.
- [Forgejo official Docker installation docs](https://forgejo.org/docs/latest/admin/installation-docker/) — deployment model; accessed 2026-05-16.
- [Forgejo Actions docs](https://forgejo.org/docs/latest/user/actions/) and [Forgejo packages docs](https://forgejo.org/docs/latest/user/packages/) — CI/CD and registry support; accessed 2026-05-16.
- [Forgejo configuration cheat sheet](https://forgejo.org/docs/latest/admin/config-cheat-sheet/) — federation default, experimental warning, and domain-availability caveat; accessed 2026-05-16.
- [Forgejo Gitea migration guide](https://forgejo.org/docs/latest/admin/upgrade/from-gitea/) — supported Gitea migration path, backup guidance, and database migration expectations; accessed 2026-05-16.
- [Forgejo homepage](https://forgejo.org/) and [Forgejo Codeberg repository](https://codeberg.org/forgejo/forgejo) — governance/federation/privacy positioning and current Codeberg star count; accessed 2026-05-16.
- [Gogs GitHub repository](https://github.com/gogs/gogs) and repository docs — current star count, feature posture, and installation references; accessed 2026-05-16.

*[Compare developer tools and DevOps platforms on PkgPulse →](https://www.pkgpulse.com)*

*See also: [AVA vs Jest](/compare/ava-vs-jest) and [Pulumi vs SST vs CDKTF 2026](/guides/pulumi-vs-sst-vs-cdktf-infrastructure-as-code-2026), [Coolify vs CapRover vs Dokku (2026)](/guides/coolify-vs-caprover-vs-dokku-self-hosted-paas-2026).*
