Own Your Code, Own Your Infrastructure
Forgejo is a self-hosted Git service that gives developers full control over their repositories, CI/CD pipelines, and team access – without sending a single line of code to a third-party platform. It is a community-driven fork of Gitea, built to stay free and open-source, and it runs comfortably on modest hardware without demanding a dedicated server farm.

What Forgejo Actually Is and Why It Matters
GitHub is convenient, but convenience comes with trade-offs. Your repositories live on someone else’s infrastructure, subject to their pricing changes, terms of service updates, and occasional outages. For solo developers, small teams, or organizations handling sensitive code, that arrangement introduces risks that are easy to ignore until something goes wrong. Forgejo removes that dependency entirely.
Forgejo offers a web interface that closely mirrors GitHub’s layout – pull requests, issues, wikis, webhooks, and package registries are all present. Migrating from GitHub or GitLab is straightforward because the API surface is familiar and most tooling designed for those platforms works with Forgejo without modification. Git clients, CI runners, and deployment scripts rarely need adjustments.
The project forked from Gitea in late 2022 after concerns arose in the community about the governance direction of Gitea’s development. The fork was explicitly designed to keep decision-making open and decentralized, with a federated future on the roadmap through ActivityPub support. That means Forgejo repositories may eventually be followable and forkable across different self-hosted instances, similar to how Mastodon handles social networking.
Resource requirements are genuinely low. A single-core virtual machine with 512MB of RAM can run Forgejo and serve a small team without strain. This makes it practical to deploy alongside other self-hosted tools – if you are already running something like Dashdot as a server dashboard, adding Forgejo to the same host is entirely reasonable.
Installing and Configuring Forgejo
The cleanest way to deploy Forgejo is with Docker Compose. Create a directory for the project, then write a docker-compose.yml file that defines the Forgejo service, maps its ports, and mounts a persistent volume for data storage. A minimal working configuration looks like this:
- Image: codeberg.org/forgejo/forgejo:latest
- Container name: forgejo
- Environment variable: USER_UID=1000 and USER_GID=1000
- Ports: 3000:3000 for the web UI, 222:22 for SSH Git operations
- Volume: mount a local directory to /data inside the container
- Restart policy: unless-stopped
Run docker compose up -d and Forgejo will pull the image and start. Navigate to http://your-server-ip:3000 in a browser and you will land on the installation wizard. This one-time setup screen handles the database choice, site title, admin account creation, and base URL. SQLite works fine for personal use or very small teams. PostgreSQL is the better choice if you expect the instance to grow or if you want proper backup tooling around the database.
After the wizard completes, the admin account is live and the interface is ready. Create an organization if you want to group repositories under a shared namespace, or start pushing code directly to personal repositories. SSH key authentication works immediately – add your public key under user settings, then test with ssh -T git@your-server-ip -p 222. The response should confirm your username if the key is accepted.

Putting Forgejo behind a reverse proxy is the step most new self-hosters skip, and it causes problems later. Running the service directly on port 3000 over HTTP is fine for internal testing but not for anything production-facing. Nginx or Caddy can front the service with a proper domain name and a TLS certificate. With Caddy, the entire configuration for a domain is often four lines: the domain name, a reverse proxy directive pointing to localhost:3000, and automatic HTTPS handling that Caddy manages on its own. Update the base URL in Forgejo’s app.ini configuration file to match the public domain once the proxy is working, then restart the container.
The app.ini file lives inside the mounted data volume at /data/gitea/conf/app.ini – the path uses “gitea” for historical reasons inherited from the fork. This file controls almost every behavioral setting: email notifications, registration restrictions, rate limiting, repository size caps, and more. Disabling open registration is strongly recommended for any instance exposed to the internet. Set DISABLE_REGISTRATION = true under the [service] section and restart. New users can then only be added manually by an admin or through invitation links, depending on your preference.
Migrating Repositories and Working With Forgejo Daily
Forgejo includes a built-in migration tool under the “New Repository” menu. It can import repositories directly from GitHub, GitLab, Gitea, Bitbucket, or any other Git source by URL. The migration pulls commit history, branches, tags, issues, pull requests, labels, and milestones depending on which options are selected. For GitHub specifically, providing a personal access token during migration allows private repositories to be transferred and preserves metadata that would otherwise be lost.
Day-to-day use feels nearly identical to GitHub once repositories are in place. Forgejo Actions, the built-in CI system, uses a workflow syntax intentionally compatible with GitHub Actions. Existing workflow YAML files often run without modification after pointing the repository at a self-hosted runner. The runner is a separate binary that registers against the Forgejo instance and executes jobs in Docker containers or directly on the host. This is where self-hosting pays off clearly – no per-minute billing, no monthly quota ceilings, and no cold-start delays waiting for a shared runner pool to free up.
The question of backups deserves direct attention before relying on any self-hosted service for real work. Forgejo stores everything in the mounted data directory and the database. A working backup strategy means copying both on a schedule – compress the data directory, dump the database, and move the archives offsite. Forgejo also has a built-in admin panel command for creating consistent backups that capture configuration, repositories, and database state together. That command is forgejo admin app generateSecret – no, actually the backup command is accessible from the admin panel under the dashboard section, or via forgejo dump run inside the container with docker exec.

One detail worth knowing before you go live: Forgejo’s default avatar system makes outbound requests to fetch user avatars from external services unless libravatar or local avatar storage is configured explicitly. For a fully air-gapped or privacy-strict setup, set ENABLE_FEDERATED_AVATAR = false and DISABLE_GRAVATAR = true in app.ini. Small configuration oversights like this are the difference between a self-hosted tool that actually keeps data local and one that quietly phones home in ways that defeat the original purpose of running your own infrastructure.





