To self-host a Git forge at home, run Forgejo (a free software forge with repositories, issues, pull requests and built-in CI) in a container or as a binary, behind a reverse proxy with TLS. Put CI runners on separate, isolated machines, close registration, require two factor authentication, and back up with forgejo dump plus a database dump you have restored.
This guide goes one level deeper than our walkthrough on how to build a tiny data center at home and our guide to a home NAS with ZFS, snapshots and backups. We read the Forgejo installation, upgrade, command line, configuration cheat sheet, reverse proxy, database preparation and Actions documentation at forgejo.org, the Forgejo announcements of December 2022 and February 2024, the Forgejo release schedule and FAQ, the Codeberg documentation, the git-shell, git-daemon and git-bundle manual pages at git-scm.com, the PostgreSQL and SQLite backup documentation, the OpenSSF Source Code Management Platform Configuration Best Practices, NIST SP 800-218 (the Secure Software Development Framework), and the CycloneDX specification. Every setting name, default and date below comes from those pages. Hosted code platforms are described here by category only.
Key takeaways
- Self-hosting gives you control of your code and CI, and makes you responsible for patching, backups, availability, abuse and build security. Decide first whether the instance is private (LAN or VPN only) or public; most other choices follow from that.
- Forgejo started on 15 December 2022 as a soft fork of Gitea, with the non-profit Codeberg e.V. as custodian of its domains. It announced on 15 February 2024 that it would become a hard fork. Versions 9.0 and later are licensed GPL v3 or later; versions up to 8.0 were MIT.
- Forgejo ships a stable release every quarter and a long term support (LTS) release each spring. On the published schedule, 17.0 is the current stable release (15 October 2026) and 15.0 is the current LTS (supported until 15 July 2027).
- SQLite is fine for a household instance, but Forgejo runs it in WAL mode, which keeps recent writes in
forgejo.db-walbeside the main file. Never back it up by copyingforgejo.dbwhile the service is running. PostgreSQL adds a server to run, butpg_dumpgives you consistent online dumps. - Forgejo does not run CI jobs itself. The separately installed Forgejo Runner does, and the Forgejo documentation says plainly that the runner performs remote code execution by design. Run it on a different machine or VM from the forge, register it at the narrowest scope, keep
container.privilegedfalse, leave the per-job network isolated, and consider ephemeral one-job runners. - Hardening that matters most at home:
DISABLE_REGISTRATION = true,GLOBAL_TWO_FACTOR_REQUIREMENT = all,INSTALL_LOCK = true,REQUIRE_SIGNIN_VIEW = truefor private instances, webhooks and migrations kept away from your LAN, and a subscription to the forgejo/security-announcements repository. forgejo dumppacks repositories, data, configuration and a SQL dump into one archive. Keep a copy ofapp.iniwith itsSECRET_KEYsomewhere safe as well: the cheat sheet warns that losing that key means data encrypted by it, such as 2FA secrets, can no longer be decrypted.- A backup you have not restored is not a backup. Restore into a scratch container on a different port at least once a quarter and run
forgejo doctor check --all.
Why self-host a Git forge, and what do you take on?
A forge is the web application around Git: repositories with access control, issues, pull requests and code review, wikis, release pages, a package registry, webhooks and, increasingly, built-in CI. Git itself is distributed, so you can always push to any machine you can reach over SSH. What a forge adds is the collaboration layer and the automation.
People self-host one at home for a handful of reasons:
- Privacy and ownership. Private projects, personal notes kept in Git, infrastructure as code for a homelab, and family photos managed with large file storage never leave hardware you control.
- Mirrors and archives. A forge can pull mirrors of upstream projects you depend on, so you still have the source if an upstream repository disappears or a hosted account is suspended.
- CI you control. A self-hosted runner can build for unusual architectures, use local hardware such as a GPU or a hardware security key, and reach machines on your LAN for deployment.
What you take on is everything a hosted platform's operations team does for you:
| Responsibility | What it means at home | Where it is covered below |
|---|---|---|
| Patching | Forgejo, the runner, the database, the reverse proxy, the container engine and the host OS all need security updates | Hardening, update cadence |
| Backups | Repositories, database, configuration secrets, LFS objects, attachments and packages | Back up and restore |
| Availability | Power, disk failure, a broken upgrade, a full disk from CI artifacts | Hardware, upgrades |
| Access control | Who can register, sign in, push and trigger CI | Hardening |
| Abuse | Spam accounts, phishing repositories and crypto mining on an open runner if the instance is public | Hardening, runners |
| CI safety | Workflows are code from whoever can push or open a pull request | Runners |
The most useful early decision is whether the forge is private (LAN or VPN only) or public. The Forgejo Actions security guide notes that a LAN or VPN only instance forces an attacker to get into that network first. For a household, private is almost always right.
What is Forgejo, and how did it fork from Gitea?
Forgejo (pronounced roughly "for-JEH-yo", from the Esperanto word for forge) is a free software code forge written in Go. Its launch announcement describes a community driven project that was, at the time, a drop-in replacement for Gitea.
The announcement of 15 December 2022 says the project started in reaction to control of Gitea moving to the newly formed for-profit company Gitea Ltd without prior community consultation, after an open letter to the Gitea owners went unanswered. It also says Codeberg e.V. agreed to become the project's custodian, holding the Forgejo domain names, and that Codeberg would use Forgejo for its own service instead of Gitea.
Codeberg e.V. is a non-profit registered association in Berlin, Germany. The Forgejo FAQ says the domains are in Codeberg's custody, that Forgejo has registered no trademarks, and that governance is "collectively defined by its contributors" through the forgejo/governance repository. A security team shared between Codeberg and Forgejo handles vulnerability reports at security@forgejo.org.
Dated timeline
| Date | Event | Source |
|---|---|---|
| 15 Dec 2022 | Forgejo announced as a community fork of Gitea, with Codeberg e.V. as custodian | Forgejo blog, "Beyond coding. We forge." |
| v1.20 | Forgejo stable releases become a hard fork, per the later hard fork post | Forgejo blog, Feb 2024 |
| 29 Aug 2023 | OpenSSF publishes its Source Code Management Platform Configuration Best Practices | best.openssf.org |
| v1.21 | Forgejo Actions enabled by default | Forgejo Actions admin guide |
| 15 Feb 2024 | Forgejo announces it will become a hard fork of Gitea | Forgejo blog, "Forgejo forks its own path forward" |
| 23 Apr 2024 | Forgejo 7.0 (LTS) released; Forgejo follows semantic versioning from 7.0.0 | Release schedule, upgrade guide |
| 16 Oct 2024 | Forgejo 9.0 released; 9.0 and later are GPL v3 or later | Release schedule, FAQ |
| 16 Apr 2025 | Forgejo 11.0 (LTS) released, end of life 16 Jul 2026 | Release schedule |
| 16 Apr 2026 | Forgejo 15.0 (LTS) released, end of life 15 Jul 2027 | Release schedule |
| 15 Oct 2026 | Forgejo 17.0 released, end of life 28 Jan 2027 | Release schedule |
The hard fork post says drop-in compatibility would no longer be guaranteed, though the API would strive to stay compatible. The upgrade guide supports Gitea up to and including v1.22 in two steps: first to Forgejo v10.0.x, then to any later Forgejo version. Do not skip the intermediate step.
Release cadence and what it means for you
The upgrade guide defines three states. A stable release receives bug fixes and security fixes for three months, plus a two week overlap after the next stable release. The LTS release, the one published in the first quarter of each year, receives critical bug fixes and security support for one year and three months. Experimental builds are the development branch and should not be used in production.
For a home instance, pick one of two policies and write it down:
- Follow LTS. Upgrade once a year, from one LTS to the next, and apply LTS patch releases as they come.
- Follow stable. Upgrade every quarter, within roughly two weeks of each new release, to stay inside the support window. Features arrive sooner and each major upgrade is smaller.
Container users get help here. The Forgejo FAQ explains that images are tagged with the major version (for example 15), which tracks the latest minor and patch release, and with the minor version (for example 15.0), which tracks the latest patch release. There is deliberately no latest tag, because moving from one major version to the next "requires a manual operation and human verification".
What hardware and storage does a small instance need?
Forgejo is a single Go program, and the forge itself is light. The heavy parts of a home forge are elsewhere: Git operations on large repositories, the search indexer, large file storage (LFS), the package registry and, above all, CI jobs. Forgejo's documentation does not publish a hardware minimum, so size by what you store and run. As a homelab rule of thumb, a single board computer or old laptop with an SSD handles a household forge on SQLite, a mini PC with 8 GB or more of memory handles a small team on PostgreSQL, and the runner gets its own machine or VM sized for your builds.
Storage is the part to plan carefully:
- Repositories live under the data path (in the container image, under
/data). Git repositories compress well, but LFS objects, release attachments and container images in the package registry do not. - Actions logs and artifacts are stored by the Forgejo server. The Actions admin guide gives the defaults: logs expire after 365 days (
LOG_RETENTION_DAYS) and artifacts after 90 days (ARTIFACT_RETENTION_DAYS). On a small disk, lower both. - Runner cache is stored by the runner itself and never sent to Forgejo, which is one more reason the runner should be a separate machine with its own disk.
- Backups need roughly the size of your data again, plus history. Put them on different hardware, such as a NAS that pulls them.
If your homelab has a ZFS pool, a dataset for the forge's data volume adds checksums and cheap snapshots, though snapshots alone are not backups.
SQLite or PostgreSQL?
The database preparation page lists supported databases: MariaDB (10.6 or later) and MySQL (8.4 or later) with the InnoDB engine, PostgreSQL (14 or later), and SQLite. If no database is configured in a container install, Forgejo uses SQLite. For a home forge the real choice is SQLite or PostgreSQL.
| Factor | SQLite | PostgreSQL |
|---|---|---|
| Extra services to run and patch | None, the database is a file | A database server and its own updates |
| Good fit | One household, a few users, light CI | Several active users, many webhooks, heavy CI status traffic |
| Journal mode in Forgejo | WAL by default (SQLITE_JOURNAL_MODE), creates forgejo.db-wal and forgejo.db-shm | Server managed write-ahead log |
| Consistent online backup | sqlite3 .backup command, or stop Forgejo first | pg_dump while running |
Included in forgejo dump | Yes, as SQL | Yes, as SQL |
| Network exposure | None | Keep it on a private network; pg_hba.conf restricted to the Forgejo host |
The cheat sheet explains why the WAL detail matters: Forgejo sets SQLite to WAL mode to allow concurrent reads during writes, and WAL keeps extra files beside forgejo.db. A file level copy of only forgejo.db taken while Forgejo is running can miss committed transactions that still sit in the WAL. Use SQLite's online backup, or stop the service, or rely on forgejo dump.
If you choose PostgreSQL, the Forgejo database preparation guide shows pg_hba.conf rules that allow only the forgejo user, only to the forgejodb database, only from the Forgejo host, with scram-sha-256 password authentication. It also notes that rules are evaluated in order and the first match wins, so put your specific rule above any broad one.
How do you install Forgejo, put it behind TLS and set up SSH?
You can run Forgejo as an OCI container (Docker or Podman) or as a binary under systemd. Both are documented by the project. Containers make upgrades a tag change and keep the host clean; the binary avoids a container engine and makes SSH integration with the host's OpenSSH simpler. Pick one.
Step 1: prepare the host
Use a dedicated machine or VM with automatic OS security updates, and a LAN DNS name such as git.home.example. A private forge can still get a trusted certificate through an ACME DNS challenge on a domain you own, or from your own internal certificate authority.
The Forgejo upgrade guide notes that Docker installs need Docker 20.10.6 or later, or problems appear in unrelated places.
Step 2: run the container with PostgreSQL
This compose file follows the Forgejo Docker documentation, with three changes for a home install: the web port binds only to localhost so that only the reverse proxy can reach it, the SSH port is published on 2222, and the database password comes from a secret file instead of the literal forgejo used in the example.
# compose.yaml (based on forgejo.org/docs/latest/admin/installation/docker/)
networks:
forgejo:
external: false
services:
server:
image: codeberg.org/forgejo/forgejo:15 # LTS major tag; use 17 to follow stable
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_URI=file:/run/secrets/db_password
restart: always
networks:
- forgejo
volumes:
- ./forgejo:/data
- /etc/localtime:/etc/localtime:ro
secrets:
- db_password
ports:
- "127.0.0.1:3000:3000" # web UI, reverse proxy only
- "2222:22" # Git over SSH
depends_on:
- db
db:
image: postgres:17
container_name: forgejo-db
restart: always
environment:
- POSTGRES_USER=forgejo
- POSTGRES_DB=forgejo
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
networks:
- forgejo
volumes:
- ./postgres:/var/lib/postgresql/data
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Forgejo reads configuration from app.ini, and the container documentation says you can set any key with environment variables named FORGEJO__SECTION__KEY. The cheat sheet documents PASSWD_URI in the [database] section as a way to give Forgejo a path to a file holding the password (its example is file:/etc/forgejo/db_passwd), so the password never appears in the compose file or the container's environment listing.
The documentation also warns that the ./forgejo volume must be owned by the UID and GID you set, or the container may not start:
mkdir -p forgejo postgres secrets
openssl rand -base64 32 > secrets/db_password.txt
chmod 600 secrets/db_password.txt
sudo chown -R 1000:1000 forgejo
docker compose up -d
docker compose logs -f server
Complete the setup page once, through the reverse proxy you configure next, and create the first administrator. Then confirm INSTALL_LOCK = true is set in app.ini; the cheat sheet says that makes the installation page inaccessible.
Step 3: reverse proxy and TLS
The Forgejo reverse proxy guide includes configurations for several proxies. For nginx it shows a server block that passes requests to http://127.0.0.1:3000, sets merge_slashes off because Forgejo needs URL encoded slashes intact, and raises client_max_body_size (512M in the example) so large pushes and uploads work. A TLS version looks like this:
# /etc/nginx/conf.d/forgejo.conf
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name git.home.example;
ssl_certificate /etc/ssl/forgejo/fullchain.pem;
ssl_certificate_key /etc/ssl/forgejo/privkey.pem;
merge_slashes off;
client_max_body_size 512M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
listen [::]:80;
server_name git.home.example;
return 301 https://$host$request_uri;
}
Then tell Forgejo its public URL, and which proxy it should trust for the client address:
[server]
ROOT_URL = https://git.home.example/
HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000
SSH_DOMAIN = git.home.example
SSH_PORT = 2222
[security]
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128
The cheat sheet says ROOT_URL overrides the automatically generated public URL, which matters when the internal and external URLs differ, as they do behind a proxy. COOKIE_SECURE defaults to true when ROOT_URL is an HTTPS URL, so set ROOT_URL correctly and session cookies follow. REVERSE_PROXY_LIMIT (default 1) and REVERSE_PROXY_TRUSTED_PROXIES (default loopback only) control whether Forgejo believes the X-Forwarded-For or X-Real-IP header; if your proxy runs on another host, add only that host's address, never *.
Step 4: SSH access
Forgejo supports two SSH models, and the configuration cheat sheet documents both:
- Container or built-in SSH server. The container image includes an SSH server; you publish its port 22 on a host port such as 2222, set
SSH_PORT = 2222so clone URLs show the right port, and users connect tossh://git@git.home.example:2222/owner/repo.git. The binary install can instead use Forgejo's built-in Go SSH server withSTART_SSH_SERVER = true. - Host OpenSSH. The binary install guide creates a dedicated
gitsystem user with a disabled password. Forgejo writes anauthorized_keysfile for that user (SSH_CREATE_AUTHORIZED_KEYS_FILE, default true) in which every key is forced throughforgejo serv, so a user's key can run Git operations against repositories they can access and nothing else.
Two cheat sheet defaults are worth knowing. MINIMUM_KEY_SIZE_CHECK is true, so Forgejo rejects keys smaller than the minimum size for their type. SSH_ALLOW_UNEXPECTED_AUTHORIZED_KEYS is false, so when Forgejo manages the git user's authorized_keys it refuses to start if that file contains keys it did not write; that protects you from a stray key that would bypass Forgejo's access checks.
Test from a client:
ssh -T -p 2222 git@git.home.example
git clone ssh://git@git.home.example:2222/alice/dotfiles.git
How do Forgejo Actions and runners work, and why must runners be isolated?
Forgejo Actions is the built-in CI. The admin guide says Actions runs workflows from files in a repository's .forgejo/workflows directory, and that "Forgejo does not run the jobs": the separately installed Forgejo Runner fetches jobs from the forge and executes them. Actions has been enabled by default since Forgejo v1.21, and you can turn it off with ENABLED = false in the [actions] section if you do not need CI.
The uses: key needs care. When it does not give an absolute URL, Forgejo prepends DEFAULT_ACTIONS_URL, which defaults to https://data.forgejo.org, where the project publishes actions known to work with Forgejo under free software licenses. The admin guide warns that if you point DEFAULT_ACTIONS_URL at an instance with open registration, anyone who creates a repository with a matching name there can supply the code that runs in your workflow. Leave the default, or point it at an instance where only you can create repositories, and write absolute URLs pinned to a commit for anything else.
Why the runner is the riskiest part of a home forge
The Forgejo document "Securing Forgejo Actions Deployments" opens with the central fact: remote code execution is normally the worst class of vulnerability, and for Forgejo Actions "their entire purpose is to perform remote code execution". Anyone who can get a workflow onto your runner gets whatever that runner can reach. Our posts on why a self-hosted runner executes strangers' code and on securing self-hosted runners cover this for hosted platforms. At home it is worse: the runner often sits on a flat network beside laptops and the NAS, or on the forge host itself.
The security guide walks through how an attacker gets a workflow to run:
- Direct push access. If someone can push any branch, they can add a workflow with an
on: pushtrigger to a new branch that branch protection does not cover, and it will run. - Forks and pull requests. A first pull request from a fork needs approval from a contributor before workflows run. After that, the guide says, later pull requests from the same person run automatically, including new workflows they add.
on: pull_requestworkflows from forks get no access to repository secrets. on: pull_request_target. These run the target branch's workflow, with secrets, and need careful review so they never execute code from the pull request.- Remote actions. A step that
uses:an action from another repository runs whatever that repository contains at the referenced version. The guide recommends commit pinned references and careful review of changes.
Registration scope
The runner registration page lists four scopes, from widest to narrowest. A site administrator can create a runner at /admin/actions/runners that runs jobs from every repository; an organization owner at /org/{org}/settings/actions/runners; a user at /user/settings/actions/runners; and a repository administrator at /{owner}/{repository}/settings/actions/runners, which runs jobs from that one repository only. Use the tightest scope that works, and no global runner if friends have accounts.
Registration in the web interface creates a UUID and a secret token, which act as the runner's username and password. You paste them into the runner's configuration file:
server:
connections:
forgejo:
url: https://git.home.example/
uuid: 00000000-0000-0000-0000-000000000000 # from the Create new runner dialog
token: REPLACE_WITH_TOKEN_FROM_DIALOG
Treat that token like a password. The security guide warns that with the host label, a job can read the runner's .runner state file, and anyone who copies it can impersonate the runner and receive jobs, including their secrets.
Label types and their isolation
Runner labels decide where a job runs. The configuration guide defines the format <label-name>:<label-type>://<default-image> and three label types:
| Label type | Where steps run | Isolation from the runner host | Resource limits available | Home use |
|---|---|---|---|---|
docker (Docker or Podman) | In a container from the label's image, as root inside the container | Container boundary; strong only with privileged: false, an isolated network and no volumes | container.options such as --memory=1g and --cpus=2 | Default choice |
lxc | In an LXC container from a template | Container boundary | The guide says no mechanisms to restrict CPU, memory, disk or network I/O | Only with a reason |
host | Directly on the runner host as the runner's user | None | None in the runner; only what systemd or the OS imposes | Only for your own trusted repositories, on a disposable machine |
The configuration guide warns that the runner does the equivalent of docker run <image>, so a downloaded image "is never updated", and recommends pinning images by digest.
The settings the security guide calls out
These keys live in the runner's YAML configuration, which you generate with forgejo-runner generate-config:
container.privilegeddefaults tofalse. If set totrue, every job, service and step container gets Docker's--privilegedflag, and the guide says a malicious workflow can then operate as root on the runner machine.container.networkdefaults to"", which creates a new isolated network for each job."host"removes isolation and lets jobs reach anything listening on the host's loopback, such as a web service you thought only the reverse proxy could reach."bridge"exposes other containers on the default bridge.container.valid_volumesdefaults to an empty list. Any volume you add can be read and changed by every job, because jobs run as root inside their containers.container.optionslets you add CPU and memory limits. The guide notes there are no limits on disk or network I/O, and no limit on how many service containers one job can start.runner.timeoutdefaults to 3 hours andrunner.capacityto 1, so a single job with an endless loop can block your runner for three hours.- Registry logins. If the Docker or Podman daemon is logged in to a private registry, any workflow can pull any image the daemon can reach. Do not log the runner's container engine into registries with private images unless every workflow author should see them.
Ephemeral runners
The security guide recommends ephemeral mode when runners are created on demand, and especially when jobs run with the host label. Register with --ephemeral and Forgejo assigns that runner at most one job and then removes it, so a stolen runner token cannot fetch another job. Forgejo enforces this, and ephemeral runners must use forgejo-runner one-job, because forgejo-runner daemon exits in ephemeral mode. A simple home pattern: boot a fresh VM from a template, register, run one-job, destroy the VM.
Network placement
Anyone who can run a workflow can reach whatever your runner can reach, including laptops on the same subnet. The guide's advice is to put the runner on its own subnet and limit cross-subnet traffic at a firewall, with tagged VLANs at the VM level if you cannot rewire. Our post on building a tiny data center at home covers VLANs for a homelab.
Step by step: set up an isolated runner
This sequence follows the Forgejo Runner installation, configuration and registration pages. It assumes a separate VM on a CI VLAN that can reach the forge over HTTPS and the internet for images, and nothing else.
- Create the VM with its own disk. Allow outbound HTTPS to the forge's address and to the registries you pull images from. Block access to your LAN subnets and to the forge's SSH and database ports.
- Install a container engine from your distribution and keep it updated.
- Create a data directory and generate the default configuration. The Forgejo runner image runs as a non-root user (UID 1000 in the documentation's
idoutput):
mkdir -p ~/runner/data/.cache
cd ~/runner
docker run --rm data.forgejo.org/forgejo/runner:13 forgejo-runner --version
docker run --rm data.forgejo.org/forgejo/runner:13 \
forgejo-runner generate-config > data/runner-config.yml
- Edit the configuration. Keep the safe defaults and add limits:
runner:
capacity: 1
timeout: 1h
labels:
- "debian:docker://data.forgejo.org/oci/debian@sha256:REPLACE_WITH_PINNED_DIGEST"
container:
privileged: false
network: "" # isolated network per job (default)
valid_volumes: [] # no host volumes (default)
options: "--memory=2g --cpus=2"
- Register the runner at repository or organization scope. In the repository or organization settings, open Actions, then Runners, then Create new runner. Copy the UUID and token into the
server.connectionsblock shown earlier. - Start the runner with
forgejo-runner daemon --config runner-config.yml, or withone-jobif you registered with--ephemeral. - Test with a harmless workflow in
.forgejo/workflows/hello.yml:
on: [push]
jobs:
hello:
runs-on: debian
steps:
- run: |
echo "runner works"
id
cat /etc/os-release
- Verify isolation. From inside a test job, confirm you cannot reach your NAS, router admin page or forge database. If you can, fix the firewall before you let anyone else push.
If you need to build container images inside jobs, the documentation's Docker Compose example uses a separate privileged Docker-in-Docker container. The security guide is clear that privileged containers hand root on that host to anyone who controls a workflow, so keep that setup on a disposable VM that holds no secrets and no data you care about.
What hardening matters for a home forge?
Here is an app.ini baseline built only from settings documented in the Forgejo configuration cheat sheet and the Actions security guide. Comments note the default where it differs.
[security]
INSTALL_LOCK = true ; default false until setup completes
GLOBAL_TWO_FACTOR_REQUIREMENT = all ; default none; values none, all, admin
MIN_PASSWORD_LENGTH = 12 ; default 8
LOGIN_REMEMBER_DAYS = 14 ; default 31
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128
DISABLE_GIT_HOOKS = true ; default true, keep it
[service]
DISABLE_REGISTRATION = true ; admins create accounts
REQUIRE_SIGNIN_VIEW = true ; default false; for private instances
DEFAULT_KEEP_EMAIL_PRIVATE = true ; default false
ENABLE_CAPTCHA = true ; only matters if registration is open
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
[repository]
MAX_CREATION_LIMIT = -1 ; set 0 on instances with untrusted users
ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = false
[admin]
DISABLE_REGULAR_ORG_CREATION = true
[webhook]
ALLOWED_HOST_LIST = external ; default; do not add private or loopback
[migrations]
ALLOW_LOCALNETWORKS = false ; default
BLOCKED_DOMAINS = ; add internal hostnames if you like
[server]
OFFLINE_MODE = true ; default true; no CDN or avatar lookups
[actions]
ENABLED = true ; set false if you do not use CI
LOG_RETENTION_DAYS = 90 ; default 365
ARTIFACT_RETENTION_DAYS = 30 ; default 90
Why each block matters:
- Registration closed. The Actions security guide lists
service.DISABLE_REGISTRATION = truefirst among ways to keep an attacker out: with registration open and Actions on, any stranger can create a repository and run code on your runner. OpenID sign up (ENABLE_OPENID_SIGNUP) follows the same setting by default. - Repository and organization limits. The same guide recommends
repository.MAX_CREATION_LIMIT = 0where users should not create repositories, and warns that any value other than-1or0also needsadmin.DISABLE_REGULAR_ORG_CREATION = true, or users can bypass the limit by creating organizations.ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = falsestops forks that would let someone open pull requests that trigger workflows. - Two factor authentication.
GLOBAL_TWO_FACTOR_REQUIREMENTacceptsnone,alloradmin. Setallon a small instance; at minimum setadmin. The Codeberg documentation, written for its Forgejo based service, describes two second factors: TOTP codes from an authenticator app, and WebAuthn security keys, which can be added once TOTP is configured. With 2FA on, Git over HTTPS uses personal access tokens instead of the account password. - Private by default.
REQUIRE_SIGNIN_VIEW = truemakes every page and the API require a login, which is what you want for a family or homelab forge. - Webhooks and migrations kept off your LAN. Both make the forge send requests on a user's behalf, a classic route to internal services. The cheat sheet says webhooks can only call hosts in
ALLOWED_HOST_LIST(defaultexternal), andALLOW_LOCALNETWORKS(default false) blocks private ranges such as RFC 1918 for migrations and pull mirrors. Keep both defaults; if you must allow one internal host, list only that host. - Server side Git hooks disabled.
DISABLE_GIT_HOOKSdefaults to true, and the cheat sheet warns that custom Git hooks can run code on the server. Leave it that way. - Password checks and privacy.
PASSWORD_CHECK_PWN(default false) checks passwords against a public online database of breached passwords. It improves password hygiene but makes the forge contact an outside service, so decide based on how private you want the instance to be.
Rate limiting and brute force
The cheat sheet offers CAPTCHA for registration and, with REQUIRE_CAPTCHA_FOR_LOGIN, for login. For request rate limiting, use the reverse proxy. With nginx, the limit_req module can cap requests per client address on the login path:
# in the http block
limit_req_zone $binary_remote_addr zone=forgejo_login:10m rate=10r/m;
# inside the server block
location = /user/login {
limit_req zone=forgejo_login burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
On a private instance reachable only over a VPN, the VPN is a stronger control than any rate limit.
Update cadence and security announcements
The upgrade guide tells administrators to watch the forgejo/security-announcements repository on Codeberg, or its RSS feed. Announcements give the expected release date of a security fix without revealing the vulnerability. The runner has its own security releases, announced the same way. A workable routine:
- Subscribe to the security announcements feed.
- When a security release is announced, take a dump the evening before.
- Pull the new image tag (or download the new binary), restart, and run
forgejo doctor check --all --log-file /tmp/doctor.log. - Click through your own checklist: sign in, clone over SSH, push, open a pull request, run a workflow.
- Update the runner image and the host OS on the same evening.
Before any upgrade, also run forgejo manager flush-queues; the guide says queued data is not guaranteed to be compatible between versions.
What the OpenSSF and NIST guidance adds
The OpenSSF Source Code Management Platform Configuration Best Practices (29 August 2023) is written for two large hosted platforms, but most of its recommendations map onto Forgejo: enforce two factor authentication, keep owners and administrators few (it suggests fewer than three), review accounts with no activity in six months, protect the default branch, block force pushes to it, require code review before merge, require checks to pass, restrict CI runners to selected private repositories, limit workflows to trusted actions, and make webhooks use TLS. In Forgejo, branch protection rules live under the repository's Settings, then Branches, and can block force pushes and require a number of approvals before merge.
NIST SP 800-218, the Secure Software Development Framework (February 2022), asks organizations to protect all forms of code from unauthorized access and tampering (practice PS.1) and to keep the integrity of releases verifiable (PS.2). For a home forge that translates to the controls above: access control and 2FA, branch protection, separated CI, and backups you can trust.
Step by step: back up and restore, then test the restore
Forgejo's command line guide describes forgejo dump as a command that "compresses all related files and database into zip file". Options include --file, --type (zip, tar.gz, tar.zst and others), --tempdir, --database for the SQL dialect, and --skip-* flags to leave out repositories, logs, LFS data, attachments, packages or the search index.
There is no matching forgejo restore command for a full instance. You restore by putting the files back and loading the SQL. So test it before you need it.
What to back up
| Item | Where it lives | Covered by forgejo dump | Notes |
|---|---|---|---|
| Git repositories | data/forgejo-repositories (container: under /data) | Yes, unless --skip-repository | The irreplaceable part |
| Database | SQLite file or PostgreSQL | Yes, as SQL | Also take a native dump |
app.ini with SECRET_KEY and INTERNAL_TOKEN | custom/conf/app.ini | Yes | Keep a separate encrypted copy; losing SECRET_KEY breaks 2FA secrets |
| LFS objects, attachments, packages, avatars | Data path | Yes, unless skipped | Can be large |
| Actions logs and artifacts | Data path | Part of data | Usually expendable |
| Runner configuration and token | Runner host | No | Re-register instead of restoring tokens |
| Reverse proxy config and certificates | Proxy host | No | Back up with the host config |
Step 1: take a dump
For the container install, run the dump as the Forgejo user inside the container. Write it to a directory on the data volume, then copy it out:
docker exec -u git -w /tmp forgejo \
forgejo dump --type tar.zst --file /data/backups/forgejo-dump-$(date +%F).tar.zst \
--config /data/gitea/conf/app.ini
The /data/gitea/conf/app.ini path is where the container image has historically kept its configuration; confirm the path on the Site administration configuration page of your instance, as the upgrade guide advises for the custom path. Create /data/backups first and make it owned by the Forgejo user.
For a binary install under systemd:
sudo -u git forgejo dump --type tar.zst \
--config /etc/forgejo/app.ini \
--file /var/backups/forgejo/forgejo-dump-$(date +%F).tar.zst
Step 2: take a native database dump as well
The SQL inside forgejo dump is portable, but a native dump is faster to restore and is what database tools expect. The PostgreSQL documentation says pg_dump makes consistent exports even while the database is in use and does not block readers or writers. It also says that, except in simple cases, pg_dump is not the right tool for regular backups of production databases, where continuous archiving is the usual answer. A home forge is a simple case; a daily dump is enough.
PostgreSQL, using the custom format that pg_restore reads:
docker exec forgejo-db pg_dump -U forgejo -d forgejo -Fc \
> /var/backups/forgejo/forgejo-$(date +%F).pgdump
SQLite, using the .backup command of the SQLite command line shell, which writes a copy of the database to a file through SQLite itself rather than copying the file underneath it:
sqlite3 /var/lib/forgejo/data/forgejo.db \
".backup '/var/backups/forgejo/forgejo-$(date +%F).db'"
Step 3: optional repository bundles
git bundle packs a repository's objects and refs into one file that Git can clone from, and --all packages all refs. A bundle per important repository is a simple extra copy that needs nothing but Git to restore:
git clone --mirror ssh://git@git.home.example:2222/alice/dotfiles.git
git -C dotfiles.git bundle create ../dotfiles-$(date +%F).bundle --all
git bundle verify dotfiles-$(date +%F).bundle
Step 4: move the backups off the box
Copy dumps to a different machine, ideally one that pulls them with its own credentials so a compromised forge cannot delete its own history. Keep at least one copy offline or off site. Encrypt the archives, because they contain app.ini secrets and every private repository.
Step 5: test a restore into a scratch instance
Warning: the commands below create and delete directories. Run them on a scratch host or in a new empty directory, never in the live instance's data path. Before each
rmor extraction, print the path and confirm it is the scratch location.
# 1. Make an empty scratch directory and confirm where you are
RESTORE=/srv/forgejo-restore-test
sudo mkdir -p "$RESTORE" && cd "$RESTORE" && pwd
# 2. Inspect the archive before extracting
tar --zstd -tf /var/backups/forgejo/forgejo-dump-2026-10-24.tar.zst | head -50
# 3. Extract into the scratch directory
tar --zstd -xf /var/backups/forgejo/forgejo-dump-2026-10-24.tar.zst -C "$RESTORE"
ls -la "$RESTORE"
Read the archive listing. It contains the configuration file, the data directory, the repositories and a SQL file of the database; the exact names can change between versions, so map them to your layout from the listing rather than from memory.
Then build a scratch instance:
- Create a new compose project with a different name, a new empty data volume and a different host port (for example
127.0.0.1:3001), plus a new empty PostgreSQL container. - Copy the restored repositories and data directory into the new volume, and the restored
app.iniinto its configuration path. Edit onlyROOT_URL,HTTP_PORTand the database host so the copy cannot collide with production. - Load the database:
pg_restore -U forgejo -d forgejo --no-ownerwith your native dump, orpsql -U forgejo -d forgejo -fwith the SQL from the dump file. For SQLite, copy the backup file into place asforgejo.db. - Fix ownership to the Forgejo UID and GID, start the scratch instance, then run:
docker exec -u git forgejo-restore forgejo doctor check --all --log-file /tmp/doctor.log
- Sign in with an account that uses 2FA. If 2FA fails, your
SECRET_KEYdid not survive the backup. - Clone a repository, check
git logfor the latest commit you expected, and open an issue and a release attachment. - Destroy the scratch instance and note the date and the time it took.
The upgrade guide notes that restoring the backup taken before an upgrade "is easy and does not lose any information", but a problem found weeks later cannot be rolled back that way. Rehearse restores on a quiet weekend, not on the night of a failed upgrade.
How do you produce an SBOM for code on your own forge and keep dependencies current?
A software bill of materials (SBOM) is a machine readable list of the components inside a piece of software, with their versions and relationships. CISA describes an SBOM as a nested inventory, "a list of ingredients that make up software components". The two common formats are SPDX and CycloneDX; the CycloneDX specification page lists version 1.7 released on 21 October 2025, developed by the OWASP Foundation and standardized at Ecma International as ECMA-424.
Moving your code to a self-hosted forge does not change how you produce an SBOM, because SBOM tools read source trees, lockfiles and container images, not the forge. It does change where the automation runs. A practical home setup:
- Generate an SBOM in CI. Add a workflow that runs on pushes to the default branch and on a schedule, runs an SBOM generator or scanner against the checkout, and uploads the result as an artifact. Remember that artifacts expire after
ARTIFACT_RETENTION_DAYS. - Attach SBOMs to releases. When you tag a release, attach the SBOM file to the release page so you can answer "which version of X is in release Y" later.
- Scan on a schedule and keep dependencies current. New vulnerabilities are published against components that have not changed, so run a weekly scan, and run a dependency update tool as a scheduled workflow that opens pull requests, with CI required to pass before merge.
- Pin what CI pulls. Pin runner images by digest and actions by commit, as the Forgejo runner documentation recommends, and update those pins on purpose.
A scheduled scan workflow with the Safeguard CLI looks like this, assuming the job image already has the CLI installed as described in the CLI documentation:
# .forgejo/workflows/sbom-scan.yml
on:
push:
branches: [main]
schedule:
- cron: "17 3 * * 1" # Mondays 03:17
jobs:
scan:
runs-on: debian
steps:
- uses: https://data.forgejo.org/actions/checkout@REPLACE_WITH_PINNED_COMMIT
- run: safeguard scan --dir .
# then upload output/ with the upload artifact action your instance provides,
# pinned to a commit like the checkout step
safeguard scan --dir . scans the source tree and writes results as JSON under ./output. You can scan a built image with --image or an existing SBOM file with --sbom. Keep any credential the CLI needs in repository secrets, and remember that on: pull_request jobs from forks do not receive secrets, which is the behavior you want.
Self-hosted forge, bare Git over SSH or a hosted platform?
You do not need a forge to self-host Git. Git itself ships two server side tools. git-shell is a login shell for SSH accounts that, per its manual page, permits only the server side Git commands behind push and pull, plus any custom commands you place in a git-shell-commands directory. git daemon serves repositories over the unauthenticated git:// protocol and, by default, refuses to export any repository that lacks a git-daemon-export-ok file unless you pass --export-all. By default only the fetch service is enabled; the manual says push over git:// is off because the protocol has no authentication, and is meant only for a closed, friendly LAN.
| Factor | Bare Git over SSH (git-shell) | Self-hosted forge (Forgejo) | Hosted platform |
|---|---|---|---|
| Control of data and history | Full | Full | Provider holds it; export available |
| Setup effort | An hour | An evening, plus proxy and TLS | Minutes |
| Ongoing effort | OS and OpenSSH patching | Forge, runner, database, proxy, OS patching; quarterly or yearly upgrades | None for the platform; you still manage accounts and settings |
| Web UI, issues, review | None | Yes | Yes |
| Built-in CI | None; use Git hooks or an external tool | Yes, with runners you operate and secure | Yes, with provider runners or your own |
| Access control granularity | Per SSH key and file permissions | Users, teams, organizations, branch protection | Users, teams, organizations, branch protection |
| Main risks | SSH key sprawl, no review trail | Exposed instance, open registration, unsafe runners, untested backups | Account takeover, provider outage or policy change, your settings |
| Backup | Copy the bare repositories or bundles | forgejo dump plus database dump, tested restores | Provider handles availability; you still keep your own clones |
| Best fit | One person, a few private repositories | Households, homelabs, small teams that want control | Public open source and teams that do not want to run infrastructure |
Many people combine them: a private Forgejo as the system of record, push mirrors of public repositories to a hosted community forge, and Git bundles on offline media.
Common mistakes
- Leaving registration open on an internet facing instance. Spam accounts arrive quickly, and with Actions enabled each one can try to run code on your runner.
- Running the runner on the forge host. A workflow that escapes or uses the
hostlabel can then read the forge's data,app.iniand database. - Setting
container.privileged = trueto make a build work. That hands root on the runner host to anyone who controls a workflow. - Copying
forgejo.dbwhile Forgejo runs. WAL mode means the copy can be missing recent writes. Use.backup, stop the service, or use the dump. - Backing up repositories but not
app.ini. Without the originalSECRET_KEY, 2FA secrets and other encrypted data cannot be decrypted after a restore. - Using unpinned image tags in runner labels. Images are never updated once pulled, so
node:ltson the runner may be months old, and a moved tag changes what runs without review. - Allowing webhooks or migrations to private ranges. It lets any user make the forge send requests to your router, NAS or hypervisor admin pages.
- Skipping intermediate versions when migrating from Gitea. The supported path is Gitea up to v1.22, then Forgejo v10.0.x, then newer Forgejo.
- Upgrading a major version without a dump and a
flush-queues. The upgrade guide treats a full backup as a requirement for major upgrades. - Never testing a restore. You find out the dump was missing LFS data, or the key, on the worst possible day.
FAQ
What is the difference between Forgejo and Gitea?
Forgejo began in December 2022 as a community soft fork of Gitea, with Codeberg e.V. as custodian of its domains, and announced a hard fork in February 2024. The projects now have separate governance, releases and features. Migration from Gitea is supported up to Gitea v1.22 through Forgejo v10.0.x.
Can I run Forgejo on a Raspberry Pi?
The Forgejo Docker documentation includes a section on using the rootless image on a Raspberry Pi, and the binary is published for several CPU architectures. Use SQLite, keep repositories and the database on an SSD rather than an SD card, and run CI on a different machine.
Should my home Git server be exposed to the internet?
Usually not. LAN or VPN only access removes most abuse and brute force risk. If you need public access, close registration, require 2FA, rate limit the login path at the proxy, and keep the runner off any repository that strangers can push to.
How do I back up Forgejo?
Run forgejo dump, which packs repositories, data, configuration and a SQL dump of the database into one archive. Add a native database dump (pg_dump for PostgreSQL, or the SQLite .backup command), keep a separate copy of app.ini, move the archives to another machine, and restore into a scratch instance regularly to prove they work.
Does Forgejo have built-in CI?
Yes. Forgejo Actions reads workflows from .forgejo/workflows with a familiar YAML syntax, and it has been enabled by default since v1.21. Jobs are executed by the separately installed Forgejo Runner, which you must host and secure yourself.
Is it safe to run a self-hosted runner at home?
It is safe enough for your own trusted repositories if the runner is on its own VM or machine, on an isolated network segment, with privileged off, the default per-job network, no host volumes and resource limits. It is not safe to let strangers trigger jobs on a runner that sits on your home LAN.
How do I enforce two factor authentication for every user?
Set GLOBAL_TWO_FACTOR_REQUIREMENT = all in the [security] section of app.ini. The other values are none (the default) and admin, which requires 2FA only for administrators.
How often should I update Forgejo?
Pick a policy. On a stable release you must move to the next one within about two weeks of its release, every quarter. On LTS you can upgrade once a year and apply patch releases. Either way, subscribe to the forgejo/security-announcements repository and patch promptly when a security release is announced.
What happens to my repositories if the forge breaks?
Every clone holds full history, so your laptops are partial backups of the code. Without a forge backup you lose everything around the code: issues, reviews, releases, packages, wikis and settings.
Where Safeguard fits
Safeguard does not host Git, run a forge, manage runners, harden servers or back anything up, and this post describes no Forgejo specific Safeguard feature. The fit is with the code on your forge and the images your runner builds. Safeguard can scan a source tree with safeguard scan --dir ., a container image with --image, or an existing SBOM with --sbom, and write results as JSON under ./output; the platform shows which projects contain a given component and version, matches components against vulnerability databases, and uses reachability analysis to rank findings. The CLI works on files on disk, so it does not matter which forge the checkout came from. Safeguard's pull request automation works through its supported source code management integrations, so check the documentation for whether your forge is one; on a self-hosted forge the dependable route is the CLI or the local runner on a checkout.
A self-hosted forge does not change where vulnerable code comes from: the repositories on it pull open source dependencies, and the runner builds from base images that carry their own packages. The steps below apply to those dependencies and images, not to Forgejo's own binary or your host operating system, which still get their fixes from the Forgejo project and your distribution.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The demos below show Safeguard working on ordinary software projects. None of them is specific to Forgejo, and that is the point: they work on a checkout, wherever it came from.
The first demo is a real Safeguard CLI session. After install, safeguard opens the interactive terminal, you ask it to fix the lodash CVE in a repository, and it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. It ends with /permissions, /model and /scan. On a home forge you would run the same loop in a clone of one of your repositories, review the diff, and push the branch to Forgejo yourself for a pull request.
Click through it at your own pace, or open it full screen.
The local runner demo shows how to set up the runner on your own machine, approve it with its device code, and then scan from the terminal. In a homelab that machine can be the same isolated CI VM that runs your Forgejo jobs, so scans of your repositories and images happen inside your own network.
Click through it at your own pace, or open it full screen.
The third demo is a real Android session. You send work to a computer running the CLI, and approve the command on the phone with Allow once, Allow for turn or Deny. It suits a home setup where the machine with your clones sits in a cupboard and you want to approve a dependency fix from the sofa without opening a laptop.
Click through it at your own pace, or open it full screen.
To try it on your own repositories, download Safeguard and follow the CLI documentation.
Quick check
A friend with an account on your home Forgejo pushes a new branch with a workflow. Your runner is registered at your user scope and the repository is yours, with branch protection on main only. Does their workflow run?
Only if they have push access to your repository. The Forgejo Actions security guide explains that anyone who can push a new branch can add an on: push workflow there, because branch protection on main does not cover new branches. If they only fork and open a pull request, their first pull request needs approval before workflows run, and fork pull requests get no secrets.
You restore last night's dump to a new server. Repositories and issues look fine, but every user with 2FA is locked out. What did you most likely lose?
The original SECRET_KEY from app.ini. The configuration cheat sheet warns that if you lose that key, data encrypted with it, such as 2FA secrets, can no longer be decrypted. Restore the original app.ini, or reset 2FA for affected users with the admin command line.
Sources
- Forgejo: Beyond coding. We forge. (launch announcement, 15 December 2022)
- Forgejo: Forgejo forks its own path forward (15 February 2024)
- Forgejo FAQ
- Forgejo release schedule
- Forgejo upgrade guide
- Forgejo installation with Docker
- Forgejo installation from binary
- Forgejo database preparation
- Forgejo reverse proxy guide
- Forgejo configuration cheat sheet
- Forgejo command line interface
- Forgejo Actions administrator guide
- Securing Forgejo Actions Deployments
- Forgejo Runner configuration
- Forgejo Runner registration
- Forgejo Runner installation with Docker
- Forgejo Actions user guide: security
- Forgejo Actions: pull request security
- Codeberg documentation: What is Codeberg?
- PostgreSQL documentation: pg_dump
- SQLite documentation: command line shell
- nginx documentation: ngx_http_limit_req_module
- Forgejo user guide: branch protection
- Codeberg documentation: setting up two-factor authentication
- Forgejo security announcements repository
- Git manual: git-shell
- Git manual: git-daemon
- Git manual: git-bundle
- OpenSSF: Source Code Management Platform Configuration Best Practices
- NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1
- CycloneDX specification overview
- CISA: Software Bill of Materials
Last reviewed: 24 October 2026. Forgejo settings, defaults and supported versions change between releases; check the configuration cheat sheet and release notes for the version you run before changing a production instance.