Workload · Hosting your team's Git forge

Your own Forgejo in Docker, on a server nobody else uses

You can run Forgejo self-hosted with one Docker Compose file: the official image, SQLite inside a single folder, the web page kept on 127.0.0.1 until you have claimed the admin account, and pushes and clones over SSH on port 222. Forgejo is a free, community-run forge (repositories, issues, pull requests, packages and CI) that grew out of Gitea, and a dedicated server gives it disks, memory and a network port that are yours alone.

The short version
  1. One compose file, data in one folder
  2. Setup page reachable only by you
  3. Builds on a separate runner machine
  4. Month to month, unmetered port

Is Forgejo the forge you want?

Pick it if you want your code, issues and reviews on a machine you control, under a licence that stays free: Forgejo has been GPL v3 or later since version 9.0, and the non-profit Codeberg e.V. looks after its domains. It started in October 2022 as a community answer to a change of control at Gitea and became a fully separate codebase in early 2024. If you already run Gitea, read our guide to self-hosting Gitea next to this one; Forgejo can take over a Gitea instance up to version 1.22.

Pick something heavier if you want an all-in-one DevOps suite with its own bundled services; our self-hosted GitLab walkthrough shows what that costs in setup and upkeep. Forgejo also does not support Windows, so plan on Linux.

How much server will it need?

Less than you might fear, though Forgejo's own docs give no memory or CPU number, so neither do we. Here is what they do say, and what it means for you:

  • Database. With no database settings, the Docker image uses SQLite, stored in your data folder. The binary guide reckons that will serve 10 or more users. Busy instances should start on MySQL/MariaDB or PostgreSQL, because switching an existing one over is hard work.
  • Disk. This is what fills up. Repositories, large files kept with Git LFS, build logs (kept a year by default) and build artifacts (kept 90 days) all land on the server.
  • Docker. The upgrade guide asks for Docker 20.10.6 or newer; for the loopback trick below, use Docker Engine 28 or newer.

The server that fits

Every dedicated server here is a whole machine: you log in as root, no one else runs on it, and nothing meters the port, which means a team cloning all day never sees a bandwidth line on the invoice. A ready server is handed over within 30 minutes of the payment being approved.

  • Small and cheap cartridges suit a forge for a handful of people on SQLite: one job, one machine.
  • Everyday servers with one processor carry Forgejo plus MariaDB or PostgreSQL for a growing team.
  • Desktop machines with a fast processor and their own graphics card are pitched at build jobs, which makes one a good home for the runner.
  • Serious workloads, the two-processor servers, are for several teams, package hosting and a database under load at the same time.

Should a bad firewall change shut you out, a remote console gets you back in. It comes with rack servers, blades and two cartridge models (m510, m710x); the remaining cartridges offer power on and off alone. You manage the server; we keep the hardware and the network running.

Step one: close the doors before Forgejo starts

Do this on a fresh Ubuntu install, before Docker runs anything. The first line makes ufw write IPv6 copies of every rule that follows; then SSH, the web ports for the proxy and Git's port 222 are allowed, and everything else is refused. The last two lines create the data folder owned by UID 1000, which the image runs Forgejo as.

sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw allow 222/tcp
sudo ufw --force enable
mkdir -p ~/forgejo/forgejo
sudo chown -R 1000:1000 ~/forgejo/forgejo

Docker and ufw do not mix the way you would expect. When compose maps a port without naming an address, Docker publishes it on 0.0.0.0 for IPv4 and [::] for IPv6, so on every address the server has. On IPv4 that published traffic never meets ufw's rules; on IPv6 it bypasses them only if you have switched on IPv6 networking inside Docker itself, and otherwise the [::] side is served by docker-proxy, which ufw does filter (hence the allow rules above). Anything private is therefore bound to 127.0.0.1, which Docker Engine 28 and newer keeps reachable from the server itself only.

Step two: the compose file

Save this as ~/forgejo/compose.yaml, swapping in your own domain. It follows the example in Forgejo's Docker guide, with two changes: the web port listens on loopback only, and the server settings come in as FORGEJO__section__KEY variables, which the official image's start scripts copy into app.ini each time the container starts (the docs add that a variable cannot remove a value already there).

services:
  server:
    image: codeberg.org/forgejo/forgejo:17
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__server__DOMAIN=git.example.com
      - FORGEJO__server__SSH_DOMAIN=git.example.com
      - FORGEJO__server__ROOT_URL=https://git.example.com/
      - FORGEJO__server__SSH_PORT=222
      - FORGEJO__service__DISABLE_REGISTRATION=true
    restart: always
    volumes:
      - ./forgejo:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - '127.0.0.1:3000:3000'
      - '222:22'

Start it from that folder with docker compose up -d. The 17 tag follows the newest 17.x release on its own; moving to 18 is a deliberate edit you make after a backup. Because the image re-applies these variables when it starts, change those settings here rather than in app.ini.

Step three: claim the admin account

Until you finish Forgejo's first-run setup page, the instance has no owner. Because the port only answers on 127.0.0.1, open it over a tunnel from your own login:

  1. On your own computer, run ssh -L 3000:127.0.0.1:3000 you@your-server.
  2. Open http://localhost:3000 in your browser.
  3. Leave the database on SQLite3 and check the domain and base URL match your compose file.
  4. Open Administrator Account Settings and create your account there.
  5. Click Install Forgejo. It sets INSTALL_LOCK = true in app.ini, and the setup page is gone for good.

Sign-ups are already closed, so you add people yourself: docker exec -u git forgejo forgejo admin user create --username sam --email sam@example.com --random-password. Forgejo makes up the password and prints it, so it never lands in your shell history; typing one with --password would. Then point nginx or another proxy at http://127.0.0.1:3000 with a certificate, as Forgejo's reverse-proxy guide shows.

Letting your team push over SSH

The image brings its own SSH service. In the official image, the SSH service inside the container keeps listening on 22 (the image's own start scripts set this, not app.ini), and that is the reason the compose file maps 222 to 22, leaving the server's port 22 for your own logins. SSH_PORT=222 only changes the port Forgejo prints in clone addresses, which read ssh://git@git.example.com:222/team/app.git. Each person adds their public key in their Forgejo settings and can push straight away. Port 222 is meant to be public, so leaving it on all addresses is fine.

Adding builds with Forgejo Actions

Workflows live in .forgejo/workflows, but Forgejo itself never runs them: a separate program, Forgejo Runner, picks up the jobs. It runs whatever code a workflow contains, which the project's own security guide calls remote code execution, so put it on its own server rather than next to your repositories. A desktop machine is a natural fit.

Create the runner in your repository's or organisation's settings rather than site-wide, so it only ever builds code you meant it to. Forgejo shows a UUID and a token; paste both into the runner's configuration under server.connections, then lock that file with chmod 600. Coming from GitHub? Our page on a self-hosted GitHub Actions runner covers that agent, and Jenkins on a dedicated server suits build pipelines kept apart from your forge.

Looking after it

  • Backups. Stopping Forgejo first is what its backup guide strongly recommends. With SQLite and the defaults, the whole instance is the ~/forgejo/forgejo folder, so docker compose stop, archive that folder, then docker compose start. For a single archive, docker exec -u git forgejo forgejo dump --file /data/forgejo-dump.zip builds one from the running container; that does not follow the guide's advice to shut down first, so treat it as a convenience copy and keep the stopped-folder archive as your real backup. It lands in that same folder, holds your app.ini secrets, and should be moved off the server.
  • Keep app.ini safe. It stores the SECRET_KEY that encrypts two-factor settings and Actions secrets. Lose it, and those stop working after a restore.
  • Upgrades. A new stable version arrives every quarter. The April release each year is a long-term release, supported for about 15 months (15.0, out on 16 April 2026, is supported until 15 July 2027). Every other release is supported for about three months, so the 17 tag means moving to 18 by late January 2027. For a major jump, run docker exec -u git forgejo forgejo manager flush-queues while it is still up, then stop and back up as above, change the tag, docker compose up -d, and finish with forgejo doctor check --all through the same docker exec.
  • Restarts. restart: always brings Forgejo back after a reboot without you lifting a finger.

Questions people ask

Is Forgejo free to run on my own server?
Yes. Versions from 9.0 on are GPL v3 or later and earlier ones were MIT. Using the official image, even to host Forgejo for other people, is very unlikely to put any duty on you, according to the project. Duties start if you hand out your own builds: then their source has to go with them.
Do I have to use Docker?
No. Forgejo publishes signed Linux binaries with a systemd setup too, and both are official. Docker keeps everything in one folder and makes upgrades a tag change, which is why this page uses it.
What stops a stranger finishing my setup page first?
The compose file publishes port 3000 on 127.0.0.1 only, so nobody outside the server can reach it. You open it through an SSH tunnel, create the admin account, and the setup page locks itself once you click Install.
Can my GitHub Actions workflows run here?
Most need only small changes. Forgejo designs its Actions to feel familiar to GitHub users without promising full compatibility: some keys are ignored, and most runners start from a lean Debian image with Node.js rather than GitHub's larger Ubuntu one. Actions named without a full URL come from data.forgejo.org by default.
Will my bill change if the team clones a lot?
No. Nobody meters the port: there is no allowance to run over and no overage line, so heavy cloning or large files leave the invoice as it was.

Give your code its own server

Choose a machine for Forgejo, and a second one for builds if you want it. Month to month, and nobody meters the port.

Get a server for your Forgejo

Read next