UP | HOME

[23may2026] setting up forgjo runner on a cloud host

Table of Contents

1. Problem

Now that git hosting is up, would like to also run github actions.

One advantage is we can setup a host runner so that we share the nix store between invocations

2. Plan

2.1. Forgejo Runner

2.1.1. Runtime environment for forgejo runner

We want to use nix for this:

# /etc/forgejo-runner/nix/flake.nix

{
  description = "Forgejo runner environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system :
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        packages.default = pkgs.forgejo-runner;
        apps.forgejo-runner = {
          type = "app";
          program = "${pkgs.forgejo-runner}/bin/forgejo-runner";
        };
        devShells.default = pkgs.mkShell {
          buildInputs = [ pkgs.forgejo-runner ];
        };
      }
    );
}

2.1.2. Add runner to forgejo instance

Create runner on https://conybeare.us/git/admin/actions/runners. There's a "create new runner" button.

We call the new runner vpn1-host.

2.1.3. Configuration

Add config =/etc/forgejo-runner/config.yaml


server:
  connections:
    host-vpn1:
      url: https://conybeare.us/git/
      uuid: 16f1f804-2f00-43f2-8c40-9ce122fd3000
      token: 9999999999999999999999999999999999999999

runner:
  labels:
    - host
  executor: host

Verify by starting runner manually

$ sudo (cd /var/lib/forgejo-runner/nix && nix run . -- daemon --config /etc/forgejo-runner/config.yaml)
INFO[2026-05-23T18:26:30Z] Starting runner daemon
INFO[2026-05-23T18:26:30Z] runner: vpn1-host, with version: v12.10.1, with labels: [host], ephemeral: false, declared successfully
INFO[2026-05-23T18:26:30Z] [poller] launched

2.1.4. Add user

$ sudo useradd --system --shell /bin/bash --home /var/lib/forgejo-runner forgejo-runner
$ mkdir -p /var/lib/forgejo-runner
$ chown -R forgejo-runner:forgejo-runner /var/lib/forgejo-runner

2.1.5. Systemd setup (forgejo-runner)

Create /etc/systemd/system/forgejo-runner.service:


[Unit]
Description=Forgejo Runner
After=network.target forgejo.service

[Service]
Type=simple
User=forgejo
WorkingDirectory=/etc/forgejo-runner
ExecStart=/nix/var/nix/profiles/default/bin/nix run \
  /etc/forgejo-runner/nix#forgejo-runner -- daemon --config /etc/forgejo-runner/config.yaml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

################################################################

Add user

$ sudo useradd --system --shell /bin/false --home /var/lib/forgejo forgejo
$ sudo mkdir -p /var/lib/forgejo /var/log/forgejo
$ sudo mkdir -p /var/lib/forgejo/lfs
$ sudo mkdir -p /var/lib/forgejo/gitea-repositories
$ sudo chown -R forgejo:forgejo /var/lib/forgejo /var/log/foregejo

Forgejo setup

$ sudo mkdir -p /etc/forgejo

create /etc/foregejo/app.ini:


[server]
ROOT_URL = https://conybeare.us/git
APP_DATA_PATH = /var/lib/forgejo
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
START_SSH_SERVER = true
SSH_LISTEN_PORT = 2222
SSH_USER = forgejo
SSH_PORT = 2222

[database]
DB_TYPE = sqlite3
PATH = /var/lib/forgejo/forgejo.db

[repository]
ROOT = /var/lib/forgejo/gitea-repositories

[ui]
DEFAULT_THEME = forgejo-dark
INSTANCE_SLOGAN = Mad Science, all the way down

[lfs]
PATH = /var/lib/forgejo/lfs
STORAGE_TYPE = local

[log]
ROOT_PATH = /var/log/forgejo
MODE = file
LEVEL = info

[mailer]
ENABLED = false

$ sudo chown forgejo:forgejo /etc/forgejo/app.ini
$ sudo chmod 600 /etc/forgejo/app.ini

2.1.6. Runtime environment for forgejo

We want to use nix for this:

# /etc/forgejo/nix/flake.nix

{
  description = "Forgejo environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system :
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        packages.default = pkgs.forgejo;
        apps.forgejo = {
          type = "app";
          program = "${pkgs.forgejo}/bin/forgejo";
        };
        devShells.default = pkgs.mkShell {
          buildInputs = [ pkgs.forgejo ];
        };
      }
    );
}

To verify syntax (will also fetch all the deps)

$ nix flake show /etc/forgejo/nix
$ ls /etc/forgejo/nix/flake.lock   # now pinned

Also turns out this environment applies to systemd starting foregejo. This doesn't get inherited by CI jobs.

To make the second part easier, create a profile in the runner account

$ sudo -u forgejo-runner bash
$ nix profile install nixpkgs#nodejs
...
$ ls /var/lib/forgego-runner/.nix-profile/bin
corepack  node  npm  npx

2.1.7. Systemd setup (Forgejo)


[Unit]
Description=Forgejo
After=network.target

[Service]
Type=simple
User=forgejo
Group=forgejo
WorkingDirectory=/var/lib/forgejo
ExecStart=/nix/var/nix/profiles/default/bin/nix run /etc/forgejo/nix#forgejo -- -c /etc/forgejo/app.ini web
Restart=always
RestartSec=5

Environment="HOME=/var/lib/forgejo"

[Install]
WantedBy=multi-user.target

then setup

$ sudo systemctl daemon-reload
$ sudo systemctl enable forgejo
$ sudo systemctl start forgejo

2.1.8. Verify status

$ sudo systemctl status forgejo
$ sudo journalctl -u forgejo -n 3
May 18 02:16:21 vpn1 nix[1043231]: Listen: http://127.0.0.1:3000
May 18 02:16:21 vpn1 nix[1043231]: AppURL(ROOT_URL): https://conybeare.us/
May 18 02:16:21 vpn1 nix[1043231]: Starting new Web server: tcp:127.0.0.1:3000 on PID: 1043231

2.2. Proxy Magic

2.2.1. Certificates

$ sudo /nix/var/nix/profiles/default/bin/nix run nixpkgs#certbot -- certonly --standalone -d conybeare.us
$ tree /etc/letsencrypt
/etc/letsencrypt/
├── accounts  [error opening dir]
├── archive  [error opening dir]
├── live  [error opening dir]
├── renewal
│   └── conybeare.us.conf
└── renewal-hooks
    ├── deploy
    ├── post
    └── pre

2.2.2. Nginx user

Add user

$ sudo useradd --system --shell /bin/false --home /var/cache/nginx nginx

Also working directories

$ sudo mkdir -p /var/cache/nginx
$ sudo chown nginx:nginx /var/cache/nginx
$ sudo chmod 755 /var/cache/nginx

$ sudo mkdir -p /var/log/nginx
$ sudo chown nginx:nginx /var/log/nginx
$ sudo chmod 755 /var/log/nginx

2.2.3. Nginx

/etc/nginx/nginx.conf:


events { }

http {
    server {
        listen 80;
        server_name conybeare.us:
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name conybeare.us;

        ssl_certificate /etc/letsencrypt/live/conybeare.us/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/conybeare.us/privkey.pem;

        location /git/ {
            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;
        }
    }
}

2.2.4. Runtime environment for nginx

Using nix again:

/etc/nginx/nix/flake.nix:


{
  description = "Nginx environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        packages.default = pkgs.nginx;
        apps.nginx = {
          type = "app";
          program = "${pkgs.nginx}/bin/nginx";
        };
      }
    );
}

To verify syntax (and fetch deps)

$ nix flake show /etc/nginx/nix
$ ls /etc/nginx/nix/flake.lock

2.2.5. Systemd setup (Nginx)

/etc/systemd/system/nginx.service:


[Unit]
Description=Nginx
After=network.target

[Service]
Type=forking
# User=nginx # start as root, bind to ports 80/443; then drop to nginx user for workers
ExecStart=/nix/var/nix/profiles/default/bin/nix run /etc/nginx/nix#nginx -- -c /etc/nginx/nginx.conf
ExecReload=/nix/var/nix/profiles/default/bin/nix run /etc/nginx/nix#nginx -- -c /etc/nginx/nginx.conf -s reload
ExecStop=/nix/var/nix/profiles/default/bin/nix run /etc/nginx/nix#nginx -- -s quit

[Install]
WantedBy=multi-user.target

then ssetup

$ sudo systemctl daemon-reload
$ sudo systemctl restart nginx
$ sudo systemctl status nginx

$ systemctl status nginx
● nginx.service - Nginx
     Loaded: loaded (/etc/systemd/system/nginx.service; disabled; preset: enabled)
     Active: active (running) since Mon 2026-05-18 04:13:51 UTC; 4s ago
    Process: 1045381 ExecStart=/nix/var/nix/profiles/default/bin/nix run /etc/nginx/nix#nginx -- -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
   Main PID: 1045385 (nginx)
      Tasks: 2 (limit: 9489)
     Memory: 2.9M (peak: 145.4M)
        CPU: 1.281s
     CGroup: /system.slice/nginx.service
             ├─1045385 "nginx: master process /nix/store/6xi5jqgw6mqdvzk36285ywc17wnx1548-nginx-1.30.1/bin/nginx -c /etc/nginx/nginx.conf"
             └─1045386 "nginx: worker process"

May 18 04:13:50 vpn1 systemd[1]: Starting nginx.service - Nginx...
May 18 04:13:51 vpn1 nix[1045381]: nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead in /etc/nginx/nginx.conf:11
May 18 04:13:51 vpn1 systemd[1]: Started nginx.service - Nginx.

2.3. Enable-on-boot

$ sudo systemctl enable nginx forgejo

2.4. Testing

$ curl  https://conybeare.us
<!DOCTYPE html>
<html lang="en-US" data-theme="forgejo-auto">
...

2.4.1. Forgejo install (web)

Now setup forgejo by visiting https://conybeare.us

Instance Slogan It's mad science, all the way down
Repository root path /var/lib/forgejo/gitea-repositories
Git LFS root path /var/lib/forgejo/lfs
Log path /var/log/forgejo
Administrator Account username, email, …

Author: Roland Conybeare

Created: 2026-05-24 Sun 18:48

Validate