Why Self-Hosted Git Makes Sense
GitHub and GitLab are convenient until they aren’t. Rate limits, storage quotas, forced feature changes, and the simple fact that your code lives on someone else’s server are real friction points for developers, small teams, and homelab enthusiasts who want full control. Forgejo offers an alternative: a lightweight, open-source Git hosting platform you run on your own hardware, with no usage caps, no telemetry, and no subscription tier standing between you and your repository settings.
Forgejo is a community-maintained fork of Gitea, which itself grew from Gogs. It ships with a clean web interface, issue tracking, pull requests, CI/CD hooks, and SSH and HTTPS repository access – all in a single binary or container image small enough to run on a Raspberry Pi. This guide walks through installing Forgejo using Docker, configuring it behind a reverse proxy, and getting your first repository live.

What You Need Before Starting
This setup assumes you have a Linux server or VM with Docker and Docker Compose installed. A domain name or local DNS entry pointing to your server will be needed if you plan to access Forgejo from outside your local network or want HTTPS working properly. If you’re managing containers visually, you can handle this through Portainer, though this guide uses command-line Compose files for clarity. Minimum specs are modest: 1 CPU core and 512MB of RAM will run Forgejo without issue for personal or small-team use.
You’ll also want a reverse proxy in place. Nginx Proxy Manager, Caddy, and Traefik are all popular choices in self-hosted environments. Caddy is the simplest for automatic HTTPS via Let’s Encrypt and requires the least manual certificate handling. The examples here use Nginx as the proxy layer since it’s widely documented and most system administrators already have some familiarity with it. If you’re using Caddy, the configuration logic is the same, just written differently.
Installing Forgejo with Docker Compose
Create a working directory for Forgejo – something like /opt/forgejo – and inside it create a docker-compose.yml file. The Compose file defines the Forgejo container, maps the data volume, sets port bindings, and passes environment variables for initial configuration. Forgejo’s official image is hosted on codeberg.org/forgejo/forgejo rather than Docker Hub, so your image reference should reflect that.
A minimal Compose configuration looks like this: set the image to codeberg.org/forgejo/forgejo:1.21 (or whichever current release tag applies), map port 3000 for the web interface and port 22 for SSH, and mount a named volume to /data inside the container. Set the restart policy to unless-stopped so Forgejo survives reboots. You can also pass environment variables like USER_UID and USER_GID to align container file permissions with your host system user, which avoids permission headaches on the mounted volume.

Once the file is saved, run docker compose up -d from your Forgejo directory. Docker will pull the image and start the container. Give it 20-30 seconds, then open a browser and navigate to http://your-server-ip:3000. You should see the Forgejo installation wizard. This is where you set the database type – SQLite is fine for personal use; PostgreSQL is the better choice if you’re running a team instance. Fill in the site URL, administrator account credentials, and SMTP settings if you want email notifications, then click Install.
After installation completes, Forgejo redirects you to the dashboard. Your Git server is running. At this stage it’s only accessible over plain HTTP on port 3000, which is fine for a quick test but not suitable for anything beyond local testing. The next step is getting it behind a reverse proxy with HTTPS.
Configuring Nginx as a Reverse Proxy
In your Nginx configuration directory, create a new server block file for Forgejo. The server block should listen on port 443 with SSL, specify your domain name in the server_name directive, and proxy all requests to http://localhost:3000 using the standard proxy headers: X-Real-IP, X-Forwarded-For, and X-Forwarded-Proto. These headers let Forgejo know the original client IP and protocol even though traffic arrives at the app through the proxy.
If you’re handling certificates through Certbot, run certbot –nginx -d your.domain.com and it will automatically update your Nginx config with the correct SSL certificate paths and add an HTTP-to-HTTPS redirect. After the certificate is issued, reload Nginx with systemctl reload nginx and navigate to your domain. Forgejo should load over HTTPS. Go back into Forgejo’s admin panel and verify the base URL setting matches your domain exactly, including the https:// prefix, otherwise clone URLs and OAuth callbacks will break.
SSH Access and Repository Setup
Forgejo supports SSH-based Git operations natively, but there’s a port conflict to be aware of: your server’s host SSH daemon already uses port 22. The easiest fix is to move your server’s SSH daemon to a non-standard port like 2222 in /etc/ssh/sshd_config, then map container port 22 to host port 22 in your Compose file so Forgejo gets clean access to the standard SSH port. Alternatively, Forgejo supports a built-in SSH passthrough mode that lets both coexist on port 22, but it requires additional configuration in app.ini.
To test SSH access, add your public SSH key in Forgejo under Settings > SSH / GPG Keys. Then create a new repository through the web interface and clone it using the SSH URL Forgejo provides. If the clone succeeds without a password prompt, SSH is wired up correctly. HTTPS clone URLs work out of the box once the reverse proxy is configured – users authenticate with their Forgejo username and password, or a personal access token generated under Settings > Applications.

Making It Production-Ready
Forgejo stores everything – repositories, uploads, avatars, config – under the /data mount. Backing up that directory on a schedule is the single most important maintenance task. A nightly rsync to a remote location, or a cron job that tars the directory and pushes it to object storage, covers the basics. Forgejo also has a built-in dump command, forgejo admin dump, that packages the database and repo data into a zip archive, which is useful if you need a clean snapshot for migration.
For teams, Forgejo’s Organizations feature lets you group repositories and manage member permissions without giving everyone admin access. You can create organization-level teams with read, write, or admin permissions scoped to specific repositories. Webhooks are available per-repository and can trigger external CI pipelines or notify a chat system on pushes. Forgejo also ships with a built-in Actions runner, compatible with GitHub Actions workflow syntax, so you can run automated tests or build pipelines without connecting to an external CI service.
One configuration detail that catches people off guard: Forgejo’s app.ini file lives inside the /data/gitea/conf/ path within your volume (the directory is still named gitea for legacy compatibility). Any setting you can’t configure through the admin UI – custom SSH options, rate limiting, mailer configuration, repository size limits – gets set here. After editing app.ini, restart the container with docker compose restart forgejo for changes to take effect. The file is well-commented and the official Forgejo documentation maps every available key, so there’s no guesswork involved.
Frequently Asked Questions
Is Forgejo compatible with GitHub or GitLab workflows?
Forgejo supports Git over SSH and HTTPS, pull requests, webhooks, and an Actions runner compatible with GitHub Actions workflow syntax, so migration is straightforward.
Can Forgejo run on a Raspberry Pi?
Yes. Forgejo is lightweight enough to run on a Raspberry Pi 3 or 4 with at least 512MB of RAM, making it ideal for low-power homelab setups.
What database should I use with Forgejo?
SQLite works fine for personal use. For team instances with multiple users and heavy traffic, PostgreSQL is more reliable and easier to back up and scale.





