VOOZH about

URL: https://pablotron.org/articles/forgejo-setup/

⇱ Forgejo Setup


Forgejo Setup

March 28, 2026

Table of Contents

Overview

Documentation for my Forgejo server and runner setup.

Server Setup

Apache virtual host which reverse proxies to an ephemeral, rootless Podman container running as a dedicated forgejo user and managed by a systemd service.

Apache

Apache virtual host which reverse proxies to the Forgejo container bound to port 8888.

Configuration

Contents of /etc/apache2/sites-available/git.pmdn.org.conf:

<VirtualHost _default_:80>
 ServerName git.pmdn.org
 # unconditionally rewrite to https://git.pmdn.org
 RewriteEngine On
 RewriteRule ^/(.*)$ https://git.pmdn.org/$1 [R,L]
 # logging
 LogLevel warn
 CustomLog /data/www/git.pmdn.org/logs/access.log combined
 ErrorLog /data/www/git.pmdn.org/logs/error.log
</VirtualHost>
<IfModule mod_ssl.c>
 <VirtualHost *:443>
 ServerName git.pmdn.org
 # tls
 Include /etc/letsencrypt/options-ssl-apache.conf
 SSLCertificateFile /etc/letsencrypt/live/git.pmdn.org/fullchain.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/git.pmdn.org/privkey.pem
 # reverse proxy configuration
 # ref: https://forgejo.org/docs/latest/admin/setup/reverse-proxy/#apache
 ProxyRequests off
 ProxyPreserveHost on
 AllowEncodedSlashes NoDecode
 # reverse proxy to port 8888
 ProxyPass / http://localhost:8888/ timeout=5 upgrade=websocket
 ProxyPassReverse / http://localhost:8888
 # logging
 LogLevel warn
 CustomLog /data/www/git.pmdn.org/logs/access.log combined
 ErrorLog /data/www/git.pmdn.org/logs/error.log
 </VirtualHost>
</IfModule>

Download “git.pmdn.org.conf”

Notes

  • mod_rewrite is required for the Rewrite* directives.
  • mod_proxy is required for the Proxy* directives.
  • TODO: Document ProxyPass options to resolve timeout issues

Systemd Service

A systemd user service which manages the Forgejo container. The Forgejo container is ephemeral, rootless, bind mounts the named forgejo-data volume, and binds to host port 8888.

Note: Normally you would also forward a second host port (e.g. 2222) to container port 22 to expose SSH. This configuration doesn’t do that because external SSH is blocked unless users are connected via the VPN.

Configuration

Contents of ~forgejo/.config/systemd/user/forgejo.service:

[Unit]
Description=Forgejo Server
[Service]
# start ephemeral container named "forgejo" with the following
# parameters:
#
# - image: codeberg.org/forgejo/forgejo:15-rootless
# - pass UID, GID, and root URL as environment variables
# - bind mount named volume "forgejo-data"
# (a named volume is required for rootless forgejo)
# - forward host port 8888 to container port 3000
ExecStart=/usr/bin/podman run --rm --name forgejo \
 -e USER_UID=%U \
 -e USER_GID=%G \
 -e ROOT_URL=https://git.pmdn.org \
 -p 8888:3000 \
 -u %U:%G \
 -v forgejo-data:/var/lib/gitea \
 -v /etc/localtime:/etc/localtime:ro \
 codeberg.org/forgejo/forgejo:15-rootless
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
TimeoutSec=0
RestartSec=10
[Install]
# start at boot
WantedBy=default.target

Download “forgejo.service”

Notes

  • If you use the non-rootless image then the data directory is at a different path inside the container than the rootless image.
  • You HAVE to bind mount a named volume to the data directory; bind mounting a host directory to the data directory fails even if the permissions and ownership for the host directory are correct.
  • Codeberg was down when I set up Forgejo, so I used the container image from the data.forgejo.org mirror instead.
  • The service could probably be written more cleanly as a container unit.

Runner Setup

Forgejo runners.

As of this writing I have the Forgejo runner installed on the following systems:

The following sections explain how to create a dedicated runner user, install the runner binary, register the runner, configure the runner, and enable the runner systemd service.

1. Create Runner User

Steps to create and configure the runner user:

# create a dedicated `runner` user
adduser runner
# enable linger for the `runner user
sudo loginctl enable-linger runner
# enable podman socket activation for runner user
sudo systemctl --user -M runner@ enable --now podman.socket

2. Install Runner Binary

Follow these instructions to download, verify, and install the forgejo-runner binary.

If those binary installation instructions are too fiddly, you can use my install-runner.py script instead.

Example:

$ ./install-runner.py
INFO:__main__:checking for signing key EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
pub ed25519 2022-11-16 [SC]
 EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
uid [ unknown] Forgejo <contact@forgejo.org>
uid [ unknown] Forgejo Releases <release@forgejo.org>
sub ed25519 2025-01-20 [S] [expires: 2027-01-10]
sub ed25519 2025-12-16 [S] [expires: 2027-06-09]
sub cv25519 2022-11-16 [E]
INFO:__main__:fetch https://code.forgejo.org/forgejo/runner/releases/download/v12.9.0/forgejo-r
unner-12.9.0-linux-arm64 (19595448 bytes)
INFO:__main__:fetch https://code.forgejo.org/forgejo/runner/releases/download/v12.9.0/forgejo-r
unner-12.9.0-linux-arm64.asc (228 bytes)
INFO:__main__:run /usr/bin/gpg --verify /tmp/tmpehuzc618/forgejo-runner-12.9.0-linux-arm64.asc
/tmp/tmpehuzc618/forgejo-runner-12.9.0-linux-arm64
gpg: Signature made Mon 20 Apr 2026 04:43:26 PM EDT
gpg: using EDDSA key 3BF4E813F84812411DA01E5BC4186DF66F4B6750
gpg: Good signature from "Forgejo <contact@forgejo.org>" [unknown]
gpg: aka "Forgejo Releases <release@forgejo.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: EB11 4F5E 6C0D C2BC DD18 3550 A4B6 1A2D C592 3710
 Subkey fingerprint: 3BF4 E813 F848 1241 1DA0 1E5B C418 6DF6 6F4B 6750
INFO:__main__:run /usr/bin/sudo /usr/bin/install -v /tmp/tmpehuzc618/forgejo-runner-12.9.0-linu
x-arm64 /usr/local/bin/forgejo-runner
removed '/usr/local/bin/forgejo-runner'
'/tmp/tmpehuzc618/forgejo-runner-12.9.0-linux-arm64' -> '/usr/local/bin/forgejo-runner'
$ sudo systemctl --user -M runner@ restart forgejo-runner

Download “install-runner-example.txt”

3. Register Runner

Steps:

  1. Follow “Interactive Registration” instructions to register a new runner on your Forgejo server.
  2. Copy the generated YAML block from the text field. You will need it in the next section.

Example:

Register a new runner.

4. Configure Runner

Create ~runner/.config/forgejo-runner/, then use generate-config to populate config.yaml:

sudo -u runner sh -c '
 mkdir -p ~runner/.config/forgejo-runner;
 forgejo-runner generate-config > ~runner/.config/forgejo-runner/config.yaml
'

Edit ~runner/.config/forgejo-runner/config.yaml and add the generated YAML block that you copied from the text field in the Register Runner section.

At this point you’ll also want to add some runner labels. See the “Choosing Labels” section of the Forgejo Runner Administrator Guide.

As of this writing choosing labels is a bit fiddly. The labels that I am using are:

  • docker:docker://data.forgejo.org/oci/node:lts
  • ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:rust-latest
  • rust:docker://ghcr.io/catthehacker/ubuntu:rust-latest

The last two labels are act container images recommend in this post.

The act container images include development tools for languages like Go and Rust. The images also include Node, so they work as expected for common actions like actions/checkout, actions/cache and actions/upload-artifact.

Here’s an example of a fully populated server section of ~runner/.config/forgejo-runner/config.yaml:

# example "server" section with populated labels# (note: token is secret and should not be shared)server:connections:pmdn:uuid:"2738dfaa-c05f-4b91-8ee8-4868316f969a"url:"https://git.pmdn.org/"token:"e531f31be9916a906799805cc857a238c4275621"labels:- "docker:docker://data.forgejo.org/oci/node:lts"- "ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:rust-latest"- "rust:docker://ghcr.io/catthehacker/ubuntu:rust-latest"- "rust-x86-64:docker://ghcr.io/catthehacker/ubuntu:rust-latest"

Download “runner-config.yaml”

To allow external networking from jobs, you may need to do the following:

  1. Edit ~runner/.config/forgejo-runner/config.yaml.
  2. Find the container section.
  3. Change network: "" to network: "host".

To register the runner with another Forgejo instances (for example, Codeberg), follow the steps at the end of the “Interactive Registration” section.

5. Enable Runner Service

Create the ~runner/.config/systemd/user directory:

# create ~runner/.config/systemd/user/
sudo -u runner mkdir -p ~runner/.config/systemd/user

Populate ~runner/.config/systemd/user/forgejo-runner.service:

[Unit]
Description=Forgejo Runner
Documentation=https://forgejo.org/docs/latest/admin/actions/
[Service]
# %E expands to $XDG_CONFIG_HOME (ex: "~/.config")
ExecStart=/usr/local/bin/forgejo-runner daemon --config %E/forgejo-runner/config.yaml
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=%h
Restart=on-failure
TimeoutSec=0
RestartSec=10
# %t expands to $XDG_RUNTIME_DIR (ex: "/run/user/1000")
Environment=DOCKER_HOST=unix://%t/podman/podman.sock
[Install]
WantedBy=default.target

Download “forgejo-runner.service”

Enable and start the forgejo-runner user service:

# enable forgejo-runner user service
sudo systemctl --user -M runner@ enable forgejo-runner
# start forgejo-runner user service
sudo systemctl --user -M runner@ start forgejo-runner

Verify that the forgejo-runner user service is working as expected:

# check status of forgejo-runner user service
$ sudo systemctl --user -M runner@ is-active forgejo-runner
active

History

Edit history of this page.