DockerSelf-Hosting

Forgejo with Docker: your own Git server with Actions (Linux, Windows, Mac)

Before you start

  • 21 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Forgejo is a free, self-hosted Git forge: repositories, issues, pull requests, a package registry and CI ("Actions") in one small program that you run on your own machine.

Forgejo official logo

Logo: the official Forgejo project (codeberg.org/forgejo/forgejo, assets/logo.svg at v17.0.1)

Use it to keep private code off third-party platforms, to run a Git server for family, friends or a small team, or to mirror projects you depend on. The project says it runs on "nearly every machine", including a Raspberry Pi. A fresh instance with SQLite used about 115 MiB of RAM in our test.

WhatValue (official docs and releases, read October 2026)
Current versionForgejo 17.0.1, released October 2026 (latest stable). Image tag 17.0.1
LicenceGPL 3.0 or any later version (versions before 9.0 were MIT)
Time neededAbout 15 minutes for the server; 15 more for an Actions runner (our estimate)
RAM and sizeAbout 115 MiB RAM idle and a 291 MB image, measured by us on Windows with Docker Desktop and SQLite
Ports3000/TCP web (we keep it local until HTTPS), 2222/TCP Git over SSH (our choice), 80 and 443 for HTTPS
Official docsforgejo.org/docs and the repository

At a glance:

  • What it is: a hard fork of Gitea, run by a community under the non-profit Codeberg e.V. (see the next section).
  • SSH clash solved: the host already uses port 22 for its own SSH, so Forgejo's SSH goes to host port 2222.
  • LTS or latest: Forgejo ships a stable release every three months and an LTS release once a year. You pick one before the first start (table below).
  • Actions: CI that reads .forgejo/workflows. Forgejo does not run the jobs; a separate runner does.
  • Platforms: the server runs in Docker on Linux (best), Windows or Mac. The runner is supported on Linux only.
  • Privacy: the update checker is on by default and asks release.forgejo.org. You can switch it off.

Where it came from, in one honest line#

Forgejo started in December 2022, after former Gitea maintainers objected to control of Gitea moving to the for-profit company Gitea Ltd; it was a soft fork in 2023 and a hard fork from 2024, and Gitea 1.22 is the last version you can upgrade straight to Forgejo.

Stable or LTS?#

Stable (this guide: 17.0.1)LTS (15.0.11)
SupportThree months, plus two weeks after the next stableCritical bug and security fixes for one year and three months
Ends28 January 202715 July 2027
Upgrade workA manual step with a backup every quarterOne big step per year; the next LTS is 19.0 in April 2027
Tag17.0.115.0.11

The database refuses to start on an older Forgejo, so you cannot go back. Choose before you create your first repository. Everything below works with either tag.

How it fits together#

You reach Forgejo in a browser or with Git over HTTPS through a proxy, or with Git over SSH on port 2222. Everything is stored in one data folder. The runner is optional.

Diagram: a browser or git over HTTPS goes through Caddy or Traefik on port 443 to the Forgejo container; git over SSH on host port 2222 goes straight to Forgejo; Forgejo stores its data in a folder on the host and passes a job queue to an optional Forgejo Runner, which starts each job in a Docker-in-Docker container

Drawn for this guide from the official docs. Dashed boxes are optional.

Before you start#

Linux: Docker Engine and Docker Compose, any x86_64 or arm64 machine, curl and tar. Windows and Mac: Docker Desktop. Check:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Docker and Docker Compose work (the upgrade notes ask for Docker 20.10.6 or newer)
  • [ ] Host port 2222 and local port 3000 are free (ss -ltn on Linux)
  • [ ] You decided: stable (17.0.1) or LTS (15.0.11)
  • [ ] For the HTTPS section only: a DNS record for the server, and ports 80 and 443 free

Tested on October 2026: Linux = not run, Windows = Docker Desktop (container, install wizard, SSH, CLI, backup and restore), Mac = not run. HTTPS and the runner were not run on any system. The rest comes from the official docs and release files read in October 2026.

Step 1: Create the data folder#

Forgejo keeps everything in one folder. We use ~/docker_volumes/forgejo, as all DIY Lab Hub guides do.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/forgejo/data
sudo chown -R 1000:1000 ~/docker_volumes/forgejo/data
cd ~/docker_volumes/forgejo

The image runs Forgejo as user and group 1000 and the docs say the folder must belong to that ID. If you do not have sudo, run the first and third lines and check id -u shows 1000.

Windows (PowerShell)#

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\forgejo\data" | Out-Null
Set-Location "$HOME\docker_volumes\forgejo"

Mac (Terminal)#

Run in terminalbash
mkdir -p ~/docker_volumes/forgejo/data
cd ~/docker_volumes/forgejo

Check it worked. The folder exists: ls -ld data (Linux, Mac) or Get-Item data (PowerShell).

Tip: Install Docker from the official Docker documentation (docs.docker.com/engine/install). Forgejo's own docs link to it.

Step 2: Write the Compose file and start Forgejo#

This is the official example with four changes: a pinned image, the web port on 127.0.0.1 only, the SSH port moved to 2222, and restart: unless-stopped. FORGEJO__server__SSH_PORT makes the clone URLs show port 2222.

Linux#

Run in terminalbash
cat > docker-compose.yml << 'EOF'
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:17.0.1
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__server__SSH_PORT=2222
    volumes:
      - ./data:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"
EOF
docker compose up -d
docker compose ps

Windows (PowerShell)#

File contentpowershell
@'
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:17.0.1
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__server__SSH_PORT=2222
    volumes:
      - ./data:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"
'@ | Set-Content -Encoding ascii docker-compose.yml
docker compose up -d
docker compose ps

Mac (Terminal)#

Same commands as Linux. If Docker complains about the /etc/localtime line, delete that line; the container then shows UTC times.

Check it worked. After a few seconds the web server answers. Real output from our test (Docker Desktop, 17.0.1, with other host ports):

Run in terminalbash
curl -s http://127.0.0.1:3000/api/healthz
File contenttext
{
  "status": "pass",
  "description": "Forgejo: Beyond coding. We forge."
}

Before the setup wizard has run, the page at / is the installation form.

Tip: Want to reach Forgejo from other PCs at home before HTTPS? Change 127.0.0.1:3000:3000 to 3000:3000. Never forward that port on your router.

Step 3: Run the setup wizard#

Open http://localhost:3000 on the machine that runs Docker. For a remote server, open an SSH tunnel from your PC first and then use the same address.

Linux#

Run in terminalbash
ssh -N -L 3000:127.0.0.1:3000 <your-user>@<your-server-address>

Windows (PowerShell)#

Run in terminalpowershell
ssh -N -L 3000:127.0.0.1:3000 <your-user>@<your-server-address>

Mac (Terminal)#

Same command as Linux. Leave the window open, then browse to http://localhost:3000.

On the form, SQLite is already chosen: it is the docs' recommendation for low to moderate activity. Check these fields:

FieldSet it to
Server domainlocalhost for now (the HTTPS section changes it)
SSH server port2222 (already filled in from the Compose file)
Forgejo base URLhttp://localhost:3000/ for now
Administrator accountOpen this block and create your admin user here

In our 17.0.1 test, "Disable self-registration", the update checker, offline mode and "Disable Gravatar" were ticked by default. Keep self-registration disabled for a private server. Untick the update checker if you do not want the weekly version lookup.

Check it worked. You can sign in, and the API reports the version. Real output from our test:

File contenttext
{"version":"17.0.1+gitea-1.22.0"}

Tip: If you skip the admin block, the docs say the first user you register becomes the administrator. Do it yourself right away; do not leave an open registration page on the internet.

Step 4: Push your first repository over SSH#

Git over SSH goes to host port 2222, not 22. Create a key if you have none, add the public key under Settings, SSH keys, and create a repository.

Linux#

Run in terminalbash
ssh-keygen -t ed25519 -C "forgejo"
cat ~/.ssh/id_ed25519.pub
ssh -p 2222 git@<your-server-address>
git clone ssh://git@<your-server-address>:2222/<user>/<repo>.git

Windows (PowerShell)#

Run in terminalpowershell
ssh-keygen -t ed25519 -C "forgejo"
Get-Content "$HOME\.ssh\id_ed25519.pub"
ssh -p 2222 git@<your-server-address>
git clone ssh://git@<your-server-address>:2222/<user>/<repo>.git

The OpenSSH client ships with Windows 10 and 11. Git for Windows provides git.

Mac (Terminal)#

Same commands as Linux.

Check it worked. The ssh -p 2222 test prints a greeting and closes. Real output from our test:

Codetext
Hi there, doctest! You've successfully authenticated with the key named t, but Forgejo does not provide shell access.

Your user name and key name replace doctest and t. The clone URL on the repository page shows the same :2222 form.

Tip: Permission denied (publickey) almost always means the public key is not saved in Forgejo for that user, or ssh offered another key. Add -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes to the test.

Put it behind HTTPS (with or without Traefik)#

For a server with a domain, put the web interface behind HTTPS so it is reached at https://git.<your-domain> and needs no web port on the host. Home-only users can keep localhost:3000 and skip this step. Git over SSH stays on port 2222 directly: a proxy for web traffic does not carry it. Both routes need a DNS record for the domain.

First tell Forgejo its public address. Add these lines under environment: in docker-compose.yml and remove the 127.0.0.1:3000:3000 port line:

File contentyaml
      - FORGEJO__server__DOMAIN=git.<your-domain>
      - FORGEJO__server__SSH_DOMAIN=git.<your-domain>
      - FORGEJO__server__ROOT_URL=https://git.<your-domain>/

The docs' reverse proxy page warns that the links Forgejo generates break when ROOT_URL is wrong. The image's default config trusts one proxy in front of it (REVERSE_PROXY_LIMIT = 1). Note that environment variables can add and change values in app.ini but not remove them.

With Traefik#

On a server that already runs Traefik v3 with the Docker provider, add the proxy network and labels. Forgejo's docs have no Traefik page; these are standard Traefik labels, with an entrypoint named https as in our other guides. Create the network once:

Run in terminalbash
docker network create proxy

Add this to the Forgejo service, and this block at the end of the file:

File contentyaml
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.forgejo.rule=Host(`git.<your-domain>`)"
      - "traefik.http.routers.forgejo.entrypoints=https"
      - "traefik.http.routers.forgejo.tls=true"
      - "traefik.http.services.forgejo.loadbalancer.server.port=3000"

networks:
  proxy:
    external: true

Then run docker compose up -d. Check it worked. curl -I https://git.<your-domain>/ returns an HTTP status line over HTTPS and the page opens with a valid certificate.

Without Traefik: Caddy#

Choose this if you have no Traefik. Caddy gets and renews its own certificates. The Forgejo docs show this Caddyfile for a public domain: git.example.com { reverse_proxy 127.0.0.1:3000 }, and say Caddy gets certificates for the domain by itself.

CaseWhat to do
Public domainA DNS A record to the server, ports 80 and 443 open. Caddy uses Let's Encrypt
Home only, no domaintls internal: Caddy's own local certificate. Trust its root on each device

Put Forgejo on the shared proxy network (the networks lines from the Traefik block, without the labels), then run Caddy in Docker:

Run in terminalbash
docker network create proxy
mkdir -p ~/docker_volumes/caddy/{data,config}
cd ~/docker_volumes/caddy

Save as ~/docker_volumes/caddy/docker-compose.yml (the image is caddy:2.11.7, the latest Caddy release in October 2026):

File contentyaml
services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data:/data
      - ./config:/config
    networks:
      - proxy

networks:
  proxy:
    external: true

Save as ~/docker_volumes/caddy/Caddyfile for a public domain:

File contenttext
git.example.com {
    reverse_proxy forgejo:3000
}

For a home-only name, use tls internal:

File contenttext
git.home.arpa {
    tls internal
    reverse_proxy forgejo:3000
}

Start both stacks, Caddy first:

Run in terminalbash
cd ~/docker_volumes/caddy && docker compose up -d
cd ~/docker_volumes/forgejo && docker compose up -d

For the home-only certificate, copy Caddy's root out of its data folder and install it as a trusted root on each device:

Run in terminalbash
docker exec caddy find /data -name root.crt
docker cp caddy:<path-from-find> ./caddy-root.crt

On Windows import it into "Trusted Root Certification Authorities", on macOS add it in Keychain Access and choose "Always Trust", on Linux use your distribution's CA store. These device steps are standard system settings, not from the Forgejo or Caddy docs.

Check it worked. curl -I https://git.example.com/ returns an HTTP status line. For a home-only name the browser shows a padlock only after the root is trusted on that device. Windows and Mac are only clients here; the Caddy commands run on the server.

Tip: After you switch to a domain, the Forgejo base URL must match what you type in the browser. If it still says localhost, clone URLs and links point to the wrong place.

Step 5: Add a runner for Actions (optional)#

Actions are on by default. A workflow in .forgejo/workflows needs a runner, a separate program that fetches jobs from Forgejo, so it needs no open port. The runner runs other people's code, so the docs ask you to read their security page first. Our layout follows the official Docker page: the runner plus a Docker-in-Docker daemon, so jobs never touch the host's Docker.

The official runner is supported and tested on Linux (amd64 and arm64). We did not run this step on any system.

Linux#

Make the folder and generate the config. The runner image runs as an unprivileged user, so the folder must belong to ID 1001:

Run in terminalbash
mkdir -p ~/docker_volumes/forgejo-runner/data/.cache
cd ~/docker_volumes/forgejo-runner
docker run --rm data.forgejo.org/forgejo/runner:13.2.1 forgejo-runner generate-config > data/runner-config.yml
sudo chown -R 1001:1001 data
chmod 775 data/.cache
chmod g+s data/.cache

In Forgejo, sign in as admin and open /admin/actions/runners, choose Create new runner, and copy the UUID and token. Edit data/runner-config.yml (with sudo, or as 1001): put them in the server section, and set a label under runner:

File contentyaml
server:
  connections:
    forgejo:
      url: https://git.<your-domain>/
      uuid: <UUID-from-Forgejo>
      token: <TOKEN-from-Forgejo>

runner:
  labels:
    - docker:docker://node:20-bookworm

The label has the form <name>:docker://<image>. A job with runs-on: docker runs in that image. Docker images are not updated once downloaded, and the docs advise pinning them. Then write the Compose file (the official one, pinned, without the old version: line):

Run in terminalbash
cat > docker-compose.yml << 'EOF'
services:
  docker-in-docker:
    image: docker:29.9.2-dind
    container_name: forgejo-dind
    privileged: true
    command: ['dockerd', '-H', 'tcp://0.0.0.0:2375', '--tls=false']
    restart: unless-stopped

  runner:
    image: data.forgejo.org/forgejo/runner:13.2.1
    container_name: forgejo-runner
    links:
      - docker-in-docker
    depends_on:
      docker-in-docker:
        condition: service_started
    environment:
      DOCKER_HOST: tcp://docker-in-docker:2375
    user: 1001:1001
    volumes:
      - ./data:/data
    restart: unless-stopped
    command: 'forgejo-runner daemon --config runner-config.yml'
EOF
docker compose up -d
docker compose logs --tail=20 runner

Port 2375 has no TLS and is never published to the host, so only the runner can reach it.

Windows (PowerShell)#

Not covered. The runner is documented for Linux, and the Docker-in-Docker container needs privileged: true. Run this step on a Linux machine and keep using Windows as a client.

Mac (Terminal)#

Not covered, for the same reason.

Check it worked. The runner appears as online in the runner list. Then add this file to a repository as .forgejo/workflows/demo.yaml (from the official quick start) and push it:

File contentyaml
on: [push]
jobs:
  test:
    runs-on: docker
    steps:
      - run: echo All good!

The Actions tab shows a run with the output "All good!". If no run starts, the label in runs-on does not match the runner's label.

Tip: The docs warn that handing jobs the Docker socket of the same daemon that runs Forgejo lets an Action shut down the instance or create an admin account. Do not mount /var/run/docker.sock into the runner for code you do not trust.

Tip: Jobs run in separate containers, so localhost inside a job is the job itself. Use the same public HTTPS address in the runner url as in the Forgejo base URL. This is our reading of how job containers behave; the docs name the instance URL for workflows in FORGEJO_SERVER_URL.

Security and testing#

ControlHow
RegistrationKeep self-registration disabled on a private server
Web port127.0.0.1 until HTTPS, then no host port. Never forward 3000
SSH portOnly 2222 is public. Use keys; the container gives no shell
Secretsdata/gitea/conf/app.ini holds SECRET_KEY. Keep it private and in every backup
Backupschmod 600; they contain keys and all repositories
RunnerTreat it as running remote code. Pin image labels and set resource limits (container.options such as --memory=1g, from the docs' security page)
UpdatesRead the release notes; watch the security announcements

Quick tests on Linux:

Run in terminalbash
cd ~/docker_volumes/forgejo
docker compose ps
docker compose logs --tail=100 forgejo
curl -s http://127.0.0.1:3000/api/healthz
docker compose exec -u git forgejo forgejo doctor check --all

The last command is the docs' health check; ours ended with All done (checks: 27). Windows and Mac run the same docker compose lines in PowerShell or Terminal.

What leaves the machine: Git traffic you send, and the update check if you left it on. It reads a DNS record at release.forgejo.org every 168 hours by default.

Common mistakes#

These come from the official docs, the project's issue tracker and our own test.

MistakeWhy it happensDo this instead
Mapping host port 22 to the containerThe host's SSH already owns 22. Docker answers Bind for ... failed: port is already allocated (our test)Publish 2222:22 and set FORGEJO__server__SSH_PORT=2222 so clone URLs match
Copying an install command without checking the tagIssue #14887 (closed October 2026): the v17 docs page showed v16 imagesRead the tag in the release list; pin the exact version
Leaving the base URL on localhost after adding a domainThe wizard saved http://localhost:3000/ and links and clone URLs follow itSet FORGEJO__server__DOMAIN, SSH_DOMAIN and ROOT_URL to your HTTPS address
Running admin commands as rootThe image refuses: Forgejo is not supposed to be run as root. Sorry.Use docker compose exec -u git forgejo forgejo ...
Resetting a password and then finding the account lockedchange-password forces a new password at next login by default (our API call answered You must change your password.)Sign in on the web once and set your own password, or add --must-change-password=false to the command (we tested it)
Backing up while Forgejo runsThe docs warn that an inconsistent backup can lose datadocker compose down, archive data, then up -d
Updating a major version without a backupThe database cannot go back; X to X+1 needs manual verificationBack up, read the release notes, then change the tag
Letting the runner update itself unseenIssue #1725: runner 13.0 to 13.1 broke jobs that ran docker run -it in host modePin the runner tag and read its release notes
Using Docker-in-Docker with TLS and no cert mountDiscussion #380: job containers could not find the certificates, and the user lost hoursUse the docs' internal tcp://...:2375 example, or install the runner next to the Docker daemon
AI assistants: forgejo:latest, act_runner register and version: "3"Assistants copy Gitea-era examples. The registry returned 404 for a latest tag; the runner docs use forgejo-runner and mark register deprecatedUse 17.0.1 (or 17), the forgejo-runner image, config-file registration and a Compose file without version:

Tip: Before you paste any install snippet, check three things against the docs: the image tag, the port mapping and the runner name. They cause most failed setups.

Tip: After every change, read docker compose logs --tail=100 forgejo. Port, permission and URL problems print there with exact text.

Tip: Write down your admin password and keep a copy of app.ini outside the server. Without SECRET_KEY a restored instance cannot read its 2FA settings or Actions secrets.

Troubleshooting#

ProblemSolution
Container will not start after up -ddocker compose logs --tail=100 forgejo. Check that data belongs to 1000:1000 (Linux) and that ports 3000 and 2222 are free
Bind for ... failed: port is already allocatedAnother program or container uses that host port. Change the left side of the mapping, e.g. 2223:22
Permission denied (publickey)Add your public key in Forgejo, then test with ssh -p 2222 -i <key> -o IdentitiesOnly=yes git@<server>
Clone URLs show port 22 or localhostSet FORGEJO__server__SSH_PORT, DOMAIN and ROOT_URL, then docker compose up -d
Log shows Your ROOT_URL in app.ini is "http://localhost:3000/", it's unlikely matching the site you are visiting.The base URL is still localhost. Set ROOT_URL to the address you actually use
Forgejo is not supposed to be run as root. Sorry.Add -u git to docker compose exec
Job log: Cannot connect to the Docker daemon at unix:///var/run/docker.sockThe job container has no Docker. Follow the docs page on Docker access in Actions
Run says no matching runner is onlineruns-on does not match a runner label; the runner list shows the labels
HTTPS certificate errorThe name in Traefik or the Caddyfile differs from DNS, or ports 80 and 443 do not reach the server

Forgot the admin password#

On the server, set a new one. The command works only as the git user:

Run in terminalbash
cd ~/docker_volumes/forgejo
docker compose exec -u git forgejo forgejo admin user change-password --username <admin-name> --password '<new-password>'

Real output from our test: doctest's password has been successfully updated!

Known bugs (open, October 2026)#

  • Issue #15086: pushes to the built-in Docker registry randomly fail, reported on 17.0.1.
  • Issue #15075: an Actions artifact with retention-days: 1 is stored with zero days and expires at the next cleanup.
  • Issue #15059: forgejo dump on SQLite is very slow. Use the folder backup below.
  • Runner issue #1770: runner 13.2.0 fails actions/checkout@v4 with rootless Podman. This guide uses Docker.

Updates and backups#

Back up before every update. The docs say to shut Forgejo down first. With SQLite and the defaults, the data folder holds the database, repositories, storage, queues, indexes and app.ini.

Linux#

Run in terminalbash
cd ~/docker_volumes/forgejo
docker compose down
mkdir -p backups
tar -czf "backups/forgejo-$(date +%F).tar.gz" data
chmod 600 backups/forgejo-*.tar.gz
docker compose up -d

Nightly at 03:00, add this with crontab -e (the % must be escaped):

File contenttext
0 3 * * * cd ~/docker_volumes/forgejo && docker compose down && tar -czf "backups/forgejo-$(date +\%F).tar.gz" data && docker compose up -d

The service is down for a few seconds. Keep a copy on another machine; our restic guide shows one way.

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\forgejo"
docker compose down
New-Item -ItemType Directory -Force backups | Out-Null
tar -czf "backups\forgejo-$(Get-Date -Format yyyy-MM-dd).tar.gz" data
docker compose up -d

tar ships with Windows 10 and 11. To schedule it, save the lines as a .ps1 file and add a daily task in Task Scheduler.

Mac (Terminal)#

Same commands as Linux. Use launchd or cron to schedule them.

Update. Pick the new tag from the release list, then:

Run in terminalbash
cd ~/docker_volumes/forgejo
docker compose exec -u git forgejo forgejo manager flush-queues
docker compose down
# back up as above, then change the image tag in docker-compose.yml
docker compose pull
docker compose up -d
docker compose exec -u git forgejo forgejo doctor check --all

The docs ask you to flush the queues first, make a full backup, and click through your normal tasks in the web interface afterwards.

Restore test. Unpack one archive into an empty folder, never over the live one, and look for the config and the database:

Run in terminalbash
mkdir -p /tmp/restore-test && tar -xzf backups/forgejo-<date>.tar.gz -C /tmp/restore-test
ls /tmp/restore-test/data/gitea

In our test the 54 KB archive contained conf, gitea.db and the repositories, and a second container started on the unpacked copy served the same user.

Performance and resources#

The docs give no minimum RAM or CPU, so measure your own server:

  • Resources: docker stats forgejo. In our test an idle instance used about 115 MiB.
  • Database: SQLite is fine for low and moderate activity; with heavy use the docs recommend MySQL or PostgreSQL. Moving later is not trivial.
  • Cache: for a small instance the docs suggest [cache].ADAPTER = twoqueue with HOST = {"size":100, "recent_ratio":0.25, "ghost_ratio":0.5}.
  • Actions storage: job logs are kept 365 days and artifacts 90 days by default (LOG_RETENTION_DAYS, ARTIFACT_RETENTION_DAYS).
  • Runner capacity: one runner can run several jobs at once; limit job memory and CPU with container.options.
  • Health: /api/healthz reports the database and cache.

FAQ#

Is Forgejo free? Yes. It is GPL 3.0 or later. Forgejo Runner uses the same licence.

Do I need a GPU? No. It is a Go program with a web interface.

Can I run it on a Raspberry Pi? The README says yes. The runner is tested on amd64 and arm64. Docker on a Pi sometimes has trouble with bind-mount permissions; the docs show a named-volume workaround on their Docker page.

Does it send my data to the project? The update checker is the only built-in lookup we found. It reads a DNS record at release.forgejo.org and is configurable.

Can I move over from Gitea? Up to Gitea 1.22, in two steps through Forgejo 10.0.x, as the upgrade guide describes. Newer Gitea instances can migrate repositories one at a time with the in-app migration tool.

Can I log in with LDAP or another directory? The docs cover LDAP, PAM and FreeIPA sign-in. We did not test them.

Sources#

Checked against the official docs on 2026-10-31.