GuidesDevelopers and hostingCI/CD with Forgejo Runner

Run CI/CD for Forgejo with Forgejo Runner and rootless Podman

Forgejo Runner on Debian 13, running your Forgejo Actions jobs in rootless Podman under a user of its own, with tests, a cache, artifacts, a container image pushed to Forgejo and a deploy over SSH, without privileged containers.

Tested on Forgejo Runner 13.2.0 and Forgejo 15.0.10 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

What you will set up

Forgejo Actions runs your tests, builds and deploys when you push to Forgejo, with workflow files much like GitHub Actions. Forgejo itself only hands out the work. A runner picks up each job and runs it, here in a fresh rootless container under a user of its own called runner.

Forgejo Runner is made by the Forgejo project, a community that develops Forgejo in the open under the umbrella of Codeberg e.V., a non-profit association in Berlin. It is free software under the GPL, version 3.0 or later (checked on 14 October 2026).

Forgejo publishes the runner as a single program and as a container image, code.forgejo.org/forgejo/runner. With rootless Podman the program fits better: it talks straight to the runner user's Podman, and the cache it serves to jobs listens on the server's own address. In a container it would need that Podman's socket mounted into it and its cache port published, and the job containers would still be started by the same Podman. The cost is that podman auto-update does not update it; step 17 shows how.

This guide builds on the Forgejo guide, which sets up the runner in two short steps. Here it goes further. Every step below was run on a Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • Forgejo Runner 13.2.0 ran jobs for Forgejo 15.0.10 in rootless containers, two at a time.
  • A workflow checked out a small Node.js project, ran its tests on Node.js 22 and 24 side by side, and restored its npm cache on the next run. Each run kept a package as an artifact, which downloaded from the run's page. A schedule and the Run workflow button both started it.
  • A secret showed as *** in the log. A job built a container image with Buildah, without privileged mode, and pushed it to Forgejo's package registry. The next job deployed it over SSH with a key that can run one command only, and the new version answered behind Caddy.
  • A pull request from another user's fork waited for approval. Once approved, its job got none of the secrets, and its token could not push or change the repository.
  • Jobs could not reach the Podman socket or apps that listen only on the server's loopback address, and could not mount file systems.
  • The runner was updated from 13.1.0 to 13.2.0 and restored from a backup, and everything came back by itself after a reboot.

The runner used about 35 MB of memory between jobs. With two jobs running, the server used about 450 MB more. Each job image takes about 1.3 GB of disk.

Before you start

You need:

  • Forgejo set up as in the Forgejo guide, steps 1 to 6, with Caddy running. If you also did its steps 7 and 8, you have done steps 2 to 4 here. Replace your runner's settings and service with those in steps 5 and 6, keep your own server: block, and restart it;
  • ufw switched on as in the security guide. The runner opens ports for its jobs' cache, which the firewall keeps closed to the internet;
  • for the deploy in step 11, an A record and an AAAA record for hello.example.com pointing at your server.

The examples use git.example.com for Forgejo, hello.example.com for the app you deploy, anna as your user name and 203.0.113.10 for your server's IPv4 address. Replace them throughout.

Forgejo is also available in Swedish: under Settings > Appearance, choose Svenska as Default language, then Change language. This guide uses the English names of menus and buttons.

1. Check that Actions is on

Forgejo 15 has Actions switched on, also for new repositories. In a repository, Settings > Units shows Actions ("Enable integrated CI/CD pipelines with Forgejo Actions") ticked, and the repository has an Actions tab. If you switched it off for a repository, tick it there and choose Save settings.

2. Install the runner

As root, install Git, which the runner uses to download actions, and download the runner. Forgejo signs each release, so check the signature before you install it:

apt install -y git gpg curl
v=13.2.0
url=https://code.forgejo.org/forgejo/runner/releases/download/v$v/forgejo-runner-$v-linux-amd64
curl -fL -o forgejo-runner $url
curl -fL -o forgejo-runner.asc $url.asc
gpg --keyserver hkps://keys.openpgp.org --recv EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
gpg --verify forgejo-runner.asc forgejo-runner

The last command must print Good signature from "Forgejo <contact@forgejo.org>". A warning that the key is not certified is expected. 13.2.0 was the newest version when we wrote this; code.forgejo.org/forgejo/runner/releases lists newer ones. Install it:

install -m 755 forgejo-runner /usr/local/bin/forgejo-runner
rm forgejo-runner forgejo-runner.asc

3. Create the user

Jobs run code from your repositories, so the runner gets a user of its own. Still as root:

useradd -m -s /bin/bash runner
loginctl enable-linger runner
systemctl --user -M runner@ enable --now podman.socket
machinectl shell runner@

podman.socket is the runner user's Docker-compatible Podman API. The runner starts each job's container through it, as this user. Everything up to step 7 runs as runner.

Jobs usually start by fetching your code from https://git.example.com, which is this server. Rootless containers normally share the server's own address, and so cannot reach it. Give this user's containers addresses of their own:

mkdir -p ~/.config/containers

Create ~/.config/containers/containers.conf:

# Give job containers addresses of their own, so they can reach this
# server's public address, and Forgejo on it.
[network]
pasta_options = ["-a", "10.0.2.0", "-n", "24", "-g", "10.0.2.2", "--dns-forward", "10.0.2.3"]

This is the second fix in step 8 of the Podman guide, here without IPv6. It applies to the runner user only: Forgejo and the app you deploy run as other users and keep their settings.

4. Create the runner in Forgejo

In Forgejo, open https://git.example.com/admin/actions/runners and choose Create new runner. Name it, for example server-runner, and choose Create. Forgejo shows the runner's UUID and token once, and a block that starts with server:. Keep the page open for the next step.

A runner created here takes jobs from every repository. A repository's Settings > Actions > Runners has the same button, for a runner that only takes that repository's jobs.

The same page also has Registration Token (Deprecated), for the older forgejo-runner register command. It still works, and writes the token to a .runner file that every user on the server can read. The way above keeps it in a file only runner can read.

5. Configure it

Create ~/runner-config.yml, with the server: block from Forgejo at the end in place of the one below:

runner:
  # How many jobs run at the same time
  capacity: 2
  # Stop a job that runs longer than this
  timeout: 1h
  # On stop or reboot, give running jobs this long to finish
  shutdown_timeout: 10m
  labels:
    - docker:docker://data.forgejo.org/oci/node:24-trixie
    - ubuntu-latest:docker://data.forgejo.org/oci/node:24-trixie

cache:
  enabled: true

container:
  # No privileged containers, no Podman socket and no server folders in jobs
  privileged: false
  docker_host: "-"
  valid_volumes: []

server:
  connections:
    forgejo:
      url: https://git.example.com/
      uuid: 00000000-0000-0000-0000-000000000000
      token: 0000000000000000000000000000000000000000

Then make it readable for runner only, since it holds the token:

chmod 600 ~/runner-config.yml

What the settings do:

  • A workflow picks a runner with runs-on:. Both labels run the job in a container from data.forgejo.org/oci/node:24-trixie, Debian 13 with Node.js, Git and Python, which Forgejo copies from Docker Hub. ubuntu-latest lets workflows written for GitHub run as they are, in this image, which has much less installed than GitHub's own machines.
  • capacity: 2 suits two vCPUs. Raise it on a bigger server, or for jobs that mostly wait.
  • cache starts the cache server that actions/cache uses, on a random port on the server. ufw keeps that port closed to the internet.
  • The container lines are the defaults, written out so you can see them: jobs run without privileged mode, cannot reach the runner user's Podman, and cannot mount folders from the server. Step 7 explains what that gives.

forgejo-runner generate-config prints every setting, with comments.

6. Start it

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

mkdir -p ~/.config/systemd/user
[Unit]
Description=Forgejo Runner, which runs Forgejo Actions jobs in rootless Podman
After=podman.socket
Wants=podman.socket

[Service]
ExecStart=/usr/local/bin/forgejo-runner daemon -c %h/runner-config.yml
WorkingDirectory=%h
Restart=always
RestartSec=10
# A little longer than shutdown_timeout in runner-config.yml
TimeoutStopSec=15min

[Install]
WantedBy=default.target

Start it:

systemctl --user daemon-reload
systemctl --user enable --now forgejo-runner
journalctl --user -u forgejo-runner

The log says declared successfully, with the labels, and the runner page in Forgejo shows it as Idle. After changing runner-config.yml, use systemctl --user restart forgejo-runner. Do not use reload: it stopped the runner in our tests.

7. What a job can and cannot do

We checked from inside a job. Each job gets a new container on a Podman network of its own, and the runner removes both when the job ends. Root inside the container is the runner user outside it, so a job cannot read Forgejo's files or those of any other app on the server. It cannot reach the Podman socket, so it cannot start or stop containers on the server. Mounting a file system was refused. Jobs reached the internet, the server's public ports, such as 22 and 443, as any visitor would, and the runner's cache. Apps that listen only on 127.0.0.1, such as Forgejo itself on port 8089, could not be reached.

What it does not give:

  • Jobs share one kernel with the server. A container is not a virtual machine. A flaw in the kernel could let a job out, as the runner user, with the runner's token. Keep code you do not trust off this server, or give it a runner on a server of its own (step 14).
  • Anyone who can push can read the secrets. A workflow in the repository gets its secrets, and a changed workflow can send them anywhere. Forgejo hides a secret's exact value in logs, not a copy of it in another form.
  • Pull requests from forks wait for you. Forgejo held such a run until we chose Approve once or Approve always on the pull request, or Deny. Once approved, the job got none of the repository's secrets, and its token could not push, edit the repository or add labels. It still runs the fork's code on your server, with internet access, and sees the repository's variables. Read the change before you approve it.
  • Privileged mode stays off. Nothing in this guide needs it, not even building images in step 10.

8. Test a project

This part is on your computer, in the hello repository from step 9 of the Forgejo guide. Add a small Node.js app with a test. Create package.json:

{
  "name": "hello",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --test",
    "start": "node server.js"
  }
}

greet.js:

import ms from 'ms';

export function greet(name, uptimeMs) {
  return `Hello, ${name}! Up for ${ms(uptimeMs, { long: true })}.`;
}

greet.test.js:

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { greet } from './greet.js';

test('greets by name', () => {
  assert.equal(greet('Anna', 60000), 'Hello, Anna! Up for 1 minute.');
});

server.js:

import http from 'node:http';
import { greet } from './greet.js';

const started = Date.now();
http.createServer((req, res) => {
  res.end(greet('world', Date.now() - started) + '\n');
}).listen(8080);

And .gitignore:

node_modules/
*.tgz

With Node.js installed, run npm install ms@2, which adds the one dependency and writes package-lock.json. Then create .forgejo/workflows/ci.yml:

on:
  push:
  pull_request:
  schedule:
    - cron: '30 3 * * 1'
  workflow_dispatch:

jobs:
  test:
    runs-on: docker
    strategy:
      matrix:
        node: [22, 24]
    container:
      image: data.forgejo.org/oci/node:${{ matrix.node }}-trixie
    steps:
      - uses: actions/checkout@v6
      - uses: actions/cache@v5
        with:
          path: ~/.npm
          key: npm-${{ matrix.node }}-${{ hashFiles('package-lock.json') }}
      - run: npm ci
      - run: npm test
      - run: npm pack
      - uses: forgejo/upload-artifact@v5
        with:
          name: hello-node${{ matrix.node }}
          path: '*.tgz'

Commit and push everything:

git add .
git commit -m "Add an app, tests and CI"
git push

The repository's Actions tab shows the run, with one job per Node.js version, and each step's output. What the workflow does:

  • on: starts it on every push and pull request, every Monday at 03:30, and from the Run workflow button, which appears on the Actions tab when you select ci.yml. Forgejo reads the schedule in UTC, and runs it on the default branch.
  • matrix runs the job once for each Node.js version, at the same time, and container: picks the image for each. Without it, the job runs in the image of the runner's label.
  • actions/cache saves ~/.npm when the job ends and restores it the next time, while package-lock.json is unchanged. The second run's log says Cache restored from key.
  • forgejo/upload-artifact keeps the package that npm pack made. The run's page lists it under Artifacts, to download as a ZIP file. Forgejo keeps artifacts for 90 days.

9. Secrets and variables

Passwords and keys that jobs need go in secrets. In the repository, open Settings > Actions > Secrets, and choose Add secret. Settings that are not secret, such as an address, go under Variables > Add variable. A workflow reads them as ${{ secrets.NAME }} and ${{ vars.NAME }}:

      - run: ./upload.sh "$TARGET"
        env:
          TARGET: ${{ vars.UPLOAD_TARGET }}
          API_KEY: ${{ secrets.API_KEY }}

Pass them through env:, as here, rather than writing them into the command itself, so that a value with quotes or spaces cannot change the command. Forgejo replaces a secret's value with *** in the log. Nobody can read a secret back in Forgejo once it is saved, only replace it. The next two steps use both.

10. Build a container image

A job can build your app's image and push it to Forgejo's own package registry, so that you can run it anywhere with podman pull git.example.com/anna/hello.

The job's automatic token could log in to the registry, but not push: Forgejo answered authentication required. Make a token that can. In Forgejo, open your avatar's menu, Settings > Applications > Generate new token. Name it ci-packages, leave Repository and organization access at All, set package to Read and write under Select permissions, and choose Generate token. Copy it, and in the hello repository add it as the secret REGISTRY_TOKEN.

On your computer, add a Containerfile to the repository:

FROM docker.io/library/node:24-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY *.js ./
USER node
EXPOSE 8080
CMD ["node", "server.js"]

And .containerignore, which keeps those folders out of the image:

.git
node_modules

Add this job at the end of ci.yml, indented by two spaces like test::

  image:
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: docker
    steps:
      - uses: actions/checkout@v6
      - name: Install Buildah
        run: apt-get update -qq && apt-get install -y -qq buildah > /dev/null
      - name: Build the image
        run: buildah build --storage-driver vfs --isolation chroot -t hello .
      - name: Push it to Forgejo
        env:
          TOKEN: ${{ secrets.REGISTRY_TOKEN }}
        run: |
          registry=${GITHUB_SERVER_URL#https://}
          echo "$TOKEN" | buildah login -u "$GITHUB_ACTOR" --password-stdin $registry
          buildah push --storage-driver vfs hello $registry/$GITHUB_REPOSITORY:${GITHUB_SHA::7}
          buildah push --storage-driver vfs hello $registry/$GITHUB_REPOSITORY:latest

needs: test builds only when the tests pass, and the if: line only for pushes to main. Commit and push. The image appears in Forgejo under your profile's Packages tab, as hello, with two tags: latest and the commit's short ID.

Buildah builds images without a background service, and here without any extra rights. Debian's buildah package is installed in each job, which adds about half a minute. --storage-driver vfs stores the layers as plain folders: the overlay driver needs /dev/fuse, which a job does not have, and failed with fuse: device not found. --isolation chroot runs the RUN lines without starting a container inside the container. In our test the whole image job took about 75 seconds.

The image is as public as your account. If your account or organisation is private, so is the image, and whoever pulls it must log in, as in step 11.

11. Deploy over SSH

The last job logs in to the server over SSH and starts the new image. The key it uses can run one command, and nothing else: no shell, no other command, no port forwarding.

Run the app. As root, create a user for it:

useradd -m -s /bin/bash hello
loginctl enable-linger hello
machinectl shell hello@

As hello:

mkdir -p ~/.config/containers/systemd

Create ~/.config/containers/systemd/hello.container:

[Unit]
Description=Hello, deployed from Forgejo Actions

[Container]
ContainerName=hello
Image=git.example.com/anna/hello:latest
# Only Caddy, on this server, can reach the app
PublishPort=127.0.0.1:8121:8080
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

If the image is private, first log in with a token that can read packages, made as in step 10 with package set to Read. Saved in ~/.config/containers/auth.json, the login lasts across reboots:

podman login --authfile ~/.config/containers/auth.json git.example.com

Start it:

systemctl --user daemon-reload
systemctl --user start hello
curl http://127.0.0.1:8121/

It answers Hello, world! Up for 2 seconds. Go back to root with exit, switch to machinectl shell caddy@, add this block at the end of ~/Caddyfile, and restart Caddy with systemctl --user restart caddy:

hello.example.com {
    reverse_proxy 127.0.0.1:8121
}

Make the deploy key. On your computer:

ssh-keygen -t ed25519 -N '' -C deploy-hello -f hello-deploy

This makes hello-deploy, the private key, and hello-deploy.pub. As root on the server, switch to machinectl shell hello@ and create ~/.ssh/authorized_keys:

install -d -m 700 ~/.ssh
nano ~/.ssh/authorized_keys

Put in one line: the text below, a space, and the whole of hello-deploy.pub:

restrict,command="podman auto-update && podman image prune -f" ssh-ed25519 AAAA... deploy-hello
chmod 600 ~/.ssh/authorized_keys

Whatever a login with this key asks for, the server runs podman auto-update, which pulls a newer latest and restarts the app, and then removes the old image. restrict turns off port forwarding and the rest. In our test, a login with the key that asked for rm -rf ~ ran podman auto-update instead, and a port forward was refused.

Give Forgejo the key and the server's address. In the hello repository, add the whole of hello-deploy, from -----BEGIN to the last line, as the secret DEPLOY_KEY. Then delete hello-deploy from your computer, or keep it in your password manager. Add two variables: DEPLOY_HOST, with the server's address, 203.0.113.10, and DEPLOY_KNOWN_HOSTS, with the line this prints as root on the server:

echo "203.0.113.10 $(cut -d' ' -f1,2 /etc/ssh/ssh_host_ed25519_key.pub)"

That is the server's own SSH key, so the job can check that it reached the right server.

Add the job at the end of ci.yml:

  deploy:
    needs: image
    runs-on: docker
    steps:
      - name: Deploy over SSH
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          KNOWN_HOSTS: ${{ vars.DEPLOY_KNOWN_HOSTS }}
        run: |
          mkdir -p ~/.ssh
          echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
          ssh -i ~/.ssh/deploy_key hello@${{ vars.DEPLOY_HOST }}

Change 'world' in server.js to your name, commit and push. The run tests, builds, pushes and deploys, in about two minutes, and the deploy job's log ends with a table where UPDATED is true. https://hello.example.com shows the new text.

The app can live on another server just as well: create the user and the key there, and put that server's address and SSH key in the two variables.

12. Actions from GitHub's marketplace

uses: actions/checkout@v6 names no server. Forgejo then fetches the action from https://data.forgejo.org, which keeps copies of about 650 popular action repositories: actions/*, docker/* and the like, but not all of them. For any other action, write the full address:

      - uses: https://github.com/actions/github-script@v7
      - uses: https://code.forgejo.org/actions/checkout@v5

We tested:

  • actions/checkout v5 and v6, actions/cache v4 and v5, and actions/github-script from GitHub worked.
  • actions/upload-artifact v4 and later stopped with "not currently supported on GHES". Forgejo's own copy, forgejo/upload-artifact, worked in v4 and v5, and forgejo/download-artifact@v4 fetched an artifact in a later job.
  • Actions that run the docker command, such as docker/login-action, failed with "Unable to locate executable file: docker". The job image has no Docker, and jobs cannot reach Podman. Build images as in step 10 instead.

To fetch actions without a server from GitHub instead, add this line to forgejo.container from the Forgejo guide, as forgejo, and restart Forgejo:

Environment=FORGEJO__actions__DEFAULT_ACTIONS_URL=https://github.com

Forgejo saves it in its own settings file. To go back, change it to https://data.forgejo.org: removing the line is not enough.

13. Capacity and cleanup

capacity in runner-config.yml is how many jobs run at once. The matrix in step 8 used both places, and the server used about 450 MB more memory while they ran. Other jobs wait for a free place, and show as waiting on the Actions tab.

The runner removes each job's container and network when the job ends. What stays is the job images, about 1.3 GB each, and the actions it has downloaded, in ~/.cache/act. As runner, a weekly timer removes the images no job is using. Create ~/.config/systemd/user/runner-cleanup.service:

[Unit]
Description=Remove job images and build leftovers no job is using

[Service]
Type=oneshot
ExecStart=/usr/bin/podman system prune --all --force

And ~/.config/systemd/user/runner-cleanup.timer:

[Unit]
Description=Weekly cleanup of the runner's images

[Timer]
OnCalendar=Sun 04:30
Persistent=true

[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now runner-cleanup.timer

The next job after a cleanup downloads its image again, so you also get the newest one each week. podman system df shows the space used. On the app's side, the podman image prune -f in the deploy key's command removes each replaced image.

14. A second runner on another server

When jobs need more room, or code you do not trust should stay away from Forgejo, add a runner on another server. Set that server up as in the Podman guide, with ufw, and do steps 2 to 6 there. It connects out to https://git.example.com, so it needs no open port. In step 4, create a new runner: each one has its own token. Give it a label of its own, such as big:docker://data.forgejo.org/oci/node:24-trixie, and workflows that say runs-on: big run there only. Each runner has its own cache. We did not test this setup.

15. What the runner connects to

Between jobs, the runner only asks Forgejo for work, every two seconds. We recorded the server's lookups and connections during the runs in this guide:

  • data.forgejo.org, for actions with short names and for the job images;
  • github.com and code.forgejo.org, for actions with those addresses;
  • what the jobs themselves fetched: registry.npmjs.org for npm ci, deb.debian.org for Buildah, and Docker Hub for the FROM line in the Containerfile;
  • Forgejo, at git.example.com, for the code, the cache keys, the artifacts and the registry.

The runner downloads each action the first time a job uses it, and keeps it in ~/.cache/act.

16. Back up

As runner:

umask 077
mkdir -p ~/backup
tar -czf ~/backup/runner.tar.gz runner-config.yml .config/containers/containers.conf .config/systemd/user

That is all the runner needs: its settings, with the token, and its services. Copy ~/backup to another machine and keep it private. Workflows, logs, artifacts, secrets and variables are in Forgejo, and in its backup.

To restore, do steps 2 and 3 up to machinectl shell runner@, copy the backup to ~/backup, and as runner:

tar -xzf ~/backup/runner.tar.gz
systemctl --user daemon-reload
systemctl --user enable --now forgejo-runner runner-cleanup.timer

The log says declared successfully again. If the token is lost, delete the runner in Forgejo and create a new one as in step 4.

17. Update

For a new runner version, repeat step 2 with its number, then as runner:

systemctl --user restart forgejo-runner

The log shows the new version. Running jobs get up to ten minutes to finish first. Read the release notes on code.forgejo.org/forgejo/runner/releases before a new major version, such as 14, which can change settings. The job images are renewed by the weekly cleanup in step 13, Buildah is installed fresh in each job, and an action named @v6 gets every release within version 6. Forgejo itself is updated as in the Forgejo guide.

Troubleshooting

A job waits and never starts. No runner has the label in its runs-on: line, or the runner is offline. Check the label, and that the runner shows as Idle at /admin/actions/runners. As runner, journalctl --user -u forgejo-runner shows why it stopped.

The checkout fails with Failed to connect to git.example.com port 443. The job container shares the server's address. Check ~/.config/containers/containers.conf from step 3, as runner.

upload-artifact fails with "not currently supported on GHES". Use forgejo/upload-artifact@v5 in place of actions/upload-artifact.

Buildah fails with fuse: device not found. --storage-driver vfs is missing on that command.

The push fails with authentication required. The token cannot write packages, or the job used its automatic token. Use a token as in step 10, in the REGISTRY_TOKEN secret.

The deploy fails with Host key verification failed. DEPLOY_KNOWN_HOSTS does not match the server's address in DEPLOY_HOST, or the server's SSH key has changed. Print the line again as in step 11.

The runner stopped after systemctl --user reload. Start it with systemctl --user start forgejo-runner, and use restart after changing its settings.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides