Self-Host Your Code with Forgejo: Escape GitHub Without Losing Your Sanity
(Updated: )

Self-Host Your Code with Forgejo: Escape GitHub Without Losing Your Sanity

Run your own Git server with Forgejo — the free, community-led Gitea fork. Full guide to setup, migration from GitHub, CI/CD, and why it's worth it.

💡 Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

I pushed my first commit to a self-hosted Git server three years ago, partly out of principle and partly because GitHub went down during a deploy and I decided I was done depending on a Microsoft-owned service for my own code.

Was it dramatic? A bit. Did I regret it? Not once.

Today I’m running Forgejo — a community-led fork of Gitea — and it hosts everything from personal projects to this very site. Here’s how to set it up and why you might actually want to.

Why Not Just Stay on GitHub?

Look, GitHub is genuinely good. I still use it for open-source stuff where visibility matters. But for your own projects? There are real reasons to self-host:

Privacy. Your private repos are on Microsoft’s servers. They can see them, theoretically. In practice they probably don’t care about your side project, but you also can’t be certain.

Cost. GitHub Free gives you unlimited public repos and private repos with up to 3 collaborators. That’s fine — until you need more. At 10+ collaborators, you’re looking at $4/user/month minimum. Self-hosting costs you whatever your server costs.

Control. You can configure your Git server however you want. Custom webhooks, integrations that GitHub doesn’t support, your own CI runners. No waiting for GitHub to ship a feature.

The “what if GitHub disappears” scenario. Unlikely, sure. But GitLab almost shut down in 2016. Nothing lasts forever. Your code, your rules.

Why Forgejo Over Gitea?

Gitea is the original self-hosted Git app. Forgejo forked from it in late 2022 after the Gitea project’s governance got… complicated. A for-profit company tried to take control of the trademark and project direction without community input.

The community said “no thanks” and created Forgejo under a proper non-profit (Codeberg e.V.), with an actual governance model.

Functionally, they’re almost identical for daily use. But Forgejo has committed to staying free software (AGPL-3.0) and not going closed-source on features. For a self-hosting nerd, that matters.

If you’ve seen the name “Forgejo” and struggled with the pronunciation: it’s for-HEH-ho, like “hecho” in Spanish. The name is a play on “forge” (where code is made) + Spanish for “I forge.” Fun fact, I found this out the embarrassing way during a podcast recording.

What You’ll Need

  • A server with at least 1GB RAM (Forgejo is lightweight — 512MB works, but leave yourself room)
  • Docker and Docker Compose
  • A domain or subdomain pointing at your server — git.yourdomain.com is the classic choice
  • A reverse proxy for SSL (Nginx Proxy Manager, Traefik, or Caddy)

I run mine on a Hetzner CX22 (2 vCPU, 4GB RAM, €4.15/month). Forgejo uses maybe 150MB RAM under normal load. Extremely efficient.

Step 1: Docker Compose Setup

Create your Forgejo directory:

mkdir -p ~/forgejo && cd ~/forgejo

Create docker-compose.yml:

version: "3"

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:9
    container_name: forgejo
    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=${POSTGRES_PASSWORD}
      - FORGEJO__server__DOMAIN=git.yourdomain.com
      - FORGEJO__server__SSH_DOMAIN=git.yourdomain.com
      - FORGEJO__server__ROOT_URL=https://git.yourdomain.com
      - FORGEJO__server__SSH_PORT=2222
      - FORGEJO__server__SSH_LISTEN_PORT=22
      - FORGEJO__service__DISABLE_REGISTRATION=true
    restart: always
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"
    depends_on:
      - db
    networks:
      - forgejo
      - proxy

  db:
    image: postgres:16
    container_name: forgejo-db
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=forgejo
    restart: always
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks:
      - forgejo

networks:
  forgejo:
  proxy:
    external: true

Create your .env file:

echo "POSTGRES_PASSWORD=$(openssl rand -hex 32)" > .env
cat .env  # verify it generated

A couple of things worth noting here:

DISABLE_REGISTRATION=true — I set this from day one. If your Forgejo is publicly accessible and you leave registration open, you’ll get bots creating spam accounts within hours. Create your admin account during first setup, then lock registration.

SSH on port 2222 — Port 22 is usually taken by your server’s own SSH. Using 2222 for Forgejo’s SSH lets you do git clone ssh://[email protected]:2222/you/repo.git. It’s a minor annoyance but the alternative (changing your server SSH port) is worse.

PostgreSQL over SQLite — Forgejo supports SQLite for small installs, and it’s tempting because it’s simpler. Don’t. SQLite struggles under concurrent writes, and you will hit this if you run any CI that does lots of small commits. PostgreSQL is the right call.

Step 2: Start It Up

docker compose up -d

First pull takes a minute. Then check:

docker compose logs -f forgejo

Wait for:

INFO  [...] Listen: 0.0.0.0:3000

Now hit http://your-server-ip:3000 in your browser. You’ll see the Forgejo setup wizard.

Step 3: Initial Configuration Wizard

The wizard is mostly self-explanatory. Key things to fill in:

  • Database settings: Should auto-populate from your env vars
  • Site title: Your Git server name
  • Repository root path: /data/forgejo/repositories (leave default)
  • SSH server domain: git.yourdomain.com
  • Forgejo base URL: https://git.yourdomain.com

At the bottom, create your admin account. Use a strong password. This is the master key to your code.

Hit “Install Forgejo.” It takes a few seconds, then redirects you to the dashboard.

Step 4: Reverse Proxy + SSL

In Nginx Proxy Manager, add a new proxy host:

  • Domain Names: git.yourdomain.com
  • Scheme: http
  • Forward Hostname: your server IP (or forgejo if on same Docker network)
  • Forward Port: 3000
  • Enable SSL: Yes, Let’s Encrypt

Forgejo also needs port 2222 exposed for SSH. Your docker-compose.yml already maps it (2222:22), so SSH works directly without going through the reverse proxy.

Test SSH works:

ssh -T [email protected] -p 2222
# Should return: Hi [username]! You've successfully authenticated...

Step 5: Migrate Repos from GitHub

This is the part people dread but it’s actually smooth.

Option A — Migrate via Forgejo UI (recommended for most people):

In Forgejo, click +Migrate Repository. Choose GitHub as the source. You can paste a Personal Access Token from GitHub to import private repos, issues, pull requests, wikis, and even GitHub Actions definitions.

I migrated 12 repos this way. Most took under a minute. The issue history comes over cleanly, comments and all.

Option B — Bare clone (for maximum control):

# On your local machine
git clone --mirror https://github.com/you/repo.git
cd repo.git

# Update remote to Forgejo
git remote set-url origin ssh://[email protected]:2222/you/repo.git

# Push everything
git push --mirror

This gets all branches, all tags, all history. But no issues or PRs — just the code.

After migrating, update your local clones:

cd ~/your-project
git remote set-url origin ssh://[email protected]:2222/you/your-project.git
git push

Done. Your local git workflow doesn’t change at all.

Step 6: Set Up Forgejo Actions (CI/CD)

Forgejo Actions is compatible with GitHub Actions syntax. Most of your existing .github/workflows/*.yml files work with minimal changes.

Enable Actions in your Forgejo config. In docker-compose.yml, add:

- FORGEJO__actions__ENABLED=true

Then restart: docker compose up -d.

Run a Forgejo Actions runner. The runner is a separate container that picks up and executes jobs:

# Add to your docker-compose.yml
services:
  runner:
    image: code.forgejo.org/forgejo/runner:6
    container_name: forgejo-runner
    depends_on:
      - forgejo
    volumes:
      - ./runner:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - FORGEJO_INSTANCE_URL=http://forgejo:3000
      - FORGEJO_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
    restart: always
    networks:
      - forgejo

To get the RUNNER_TOKEN, go to Site Administration → Actions → Runners → Create new runner in Forgejo.

Your workflow files stay almost identical. Change the runs-on value to docker (what your self-hosted runner uses) instead of ubuntu-latest:

# Before (GitHub)
jobs:
  build:
    runs-on: ubuntu-latest

# After (Forgejo self-hosted)
jobs:
  build:
    runs-on: docker

Everything else — steps, actions, secrets — works the same.

Step 7: Backups

Your data lives in ~/forgejo/data/ and ~/forgejo/postgres/. Back both up.

Simple daily backup script:

#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR="/mnt/backups/forgejo"

mkdir -p "$BACKUP_DIR"

# Backup PostgreSQL
docker exec forgejo-db pg_dump -U forgejo forgejo | gzip > "$BACKUP_DIR/forgejo-db-$DATE.sql.gz"

# Backup data directory
tar -czf "$BACKUP_DIR/forgejo-data-$DATE.tar.gz" ~/forgejo/data/

# Keep only 30 days
find "$BACKUP_DIR" -mtime +30 -delete

echo "Forgejo backup done: $DATE"

Add this to cron: 0 3 * * * /home/devuser/scripts/forgejo-backup.sh

Forgejo repos are just bare Git repos under data/forgejo/repositories/. In the worst case, you can reconstruct everything from those files even without the database. Git is remarkably durable.

Everyday Workflow

After setup, using Forgejo feels identical to GitHub. The interface is nearly the same. Issues, PRs, code review, labels, milestones — all there.

A few differences I’ve noticed:

Email notifications — You’ll need to configure an SMTP server if you want email alerts. I use Resend (free tier: 3,000 emails/month) with a custom domain. Takes 5 minutes.

No Copilot — Obviously. But if you use Cursor, Zed, or any local AI coding tool, those work the same regardless of where your remote is.

Webhooks are better — Forgejo’s webhook support is more flexible than GitHub’s. I have webhooks triggering deploys on Coolify that I couldn’t get working cleanly on GitHub without a paid tier.

What I Wish I’d Known

Set up email before anything else. I skipped it thinking I’d do it later. Three months later, I still hadn’t. Now password resets require me to manually go into the admin panel. Not ideal.

Plan your SSH key setup early. If you have multiple machines, set up SSH keys on all of them before you migrate repos. Otherwise you’ll be migrating and suddenly can’t clone because the new origin needs a key you haven’t added.

The app.ini config file is powerful. Forgejo creates /data/forgejo/conf/app.ini for all its settings. Many things that aren’t in the UI can be configured there. The Forgejo configuration cheatsheet is your friend.

FAQ

Can I use Forgejo with VS Code / Cursor? Yes, completely. They connect to git remotes, not GitHub specifically. Change the remote URL and everything works.

What about GitHub Pages? Forgejo doesn’t have built-in static hosting, but you can set up a webhook to trigger a Coolify deployment or Caddy to serve a public/ folder. A bit more work, but doable.

Can I mirror GitHub repos to Forgejo automatically? Yes. In Forgejo, when migrating a repo, there’s a “Mirror” option that keeps the repo in sync with GitHub on a schedule. Great for keeping a backup of your open-source projects.

Is Forgejo actively developed? Very much so. Forgejo 9.0 shipped in late 2025 with major improvements to CI/CD and the API. The Codeberg community is active and responsive.

How does it handle large repos? I have a couple of repos with 5,000+ commits and multi-GB of LFS storage. Forgejo handles them fine on modest hardware. Just make sure your Docker volume has enough space.

Do I need to keep my GitHub account? That’s up to you. I kept mine active for open-source contributions and to keep my green contribution graph. But all personal projects now live on my own Forgejo instance. Best of both worlds.


Owning your code is a good feeling. Not in a paranoid “off the grid” way, just — it’s yours. The history, the issues, the CI logs. All on hardware you control, backed up by you, accessible as long as you want.

If you’re already running a homelab, adding Forgejo is low-effort compared to what you probably already manage. Give it an afternoon. You might not look back.

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.