Setting up local Git server and Forgejo front-end#

Guide should be transferable to many systems, but here using Ubuntu 24.04 on both hosts.

Create system account “git”#

This will also implicitly create the group “git”:

sudo useradd \
  --system \
  --shell /bin/bash \
  --home /srv/forgejo \
  --comment 'Forgejo Git Service' \
  git

Create ZFS datasets, tune them, set mountpoints#

Arguably overkill but I will create separate datasets for repos, data, log. Operationally this is useful to rollback forgejo via ZFS snapshot, but leave repos untouched. For light personal use a single dataset is also fine and perhaps preferable - make your call.

If you are not using ZFS, take the intent, apply it to your storage system. :)

Layout:

  • tank/services/forgejo (root container)
  • tank/services/forgejo/repos (Git data)
  • tank/services/forgejo/data (DB and runtime data)
  • tank/services/forgejo/log (logs - disposable)

Create datasets#

Here the pool ’tank’ already exists, but ‘services/*’ does not yet; use -p to create missing parents:

sudo zfs create -p tank/services/forgejo/repos
sudo zfs create -p tank/services/forgejo/data
sudo zfs create -p tank/services/forgejo/log

Create mounts#

Serve from ‘/srv’:

sudo zfs set mountpoint=/srv/forgejo tank/services/forgejo
sudo zfs set mountpoint=/srv/forgejo/repos tank/services/forgejo/repos
sudo zfs set mountpoint=/srv/forgejo/data tank/services/forgejo/data
sudo zfs set mountpoint=/srv/forgejo/log tank/services/forgejo/log

Verify mounts#

$ mount | grep forge
tank/services/forgejo on /srv/forgejo type zfs (rw,noatime,xattr,noacl,casesensitive)
tank/services/forgejo/repos on /srv/forgejo/repos type zfs (rw,noatime,xattr,noacl,casesensitive)
tank/services/forgejo/data on /srv/forgejo/data type zfs (rw,noatime,xattr,noacl,casesensitive)
tank/services/forgejo/log on /srv/forgejo/log type zfs (rw,noatime,xattr,noacl,casesensitive)

Tune datasets#

All will have atime=off, and compression=lz4, but we vary recordsize:

  • repos & data get the same tune for small files, more efficient IO and storage with recordsize=16K.
  • log will use the implicit default recordsize=128K; these files are larger and append-heavy, less metadata overhead this way.
sudo zfs set compression=lz4 tank/services/forgejo/repos
sudo zfs set atime=off tank/services/forgejo/repos
sudo zfs set recordsize=16K tank/services/forgejo/repos
sudo zfs set compression=lz4 tank/services/forgejo/data
sudo zfs set atime=off tank/services/forgejo/data
sudo zfs set recordsize=16K tank/services/forgejo/data
sudo zfs set compression=lz4 tank/services/forgejo/log
sudo zfs set atime=off tank/services/forgejo/log

Align permissions to the ‘git’ user#

sudo chown -R git:git /srv/forgejo
sudo chmod -R 750 /srv/forgejo

verify zfs#

zfs list | grep forgejo zfs get mountpoint | grep forgejo

Install some things#

Dependencies#

You might already have some, command is harmless in that case:

sudo apt update
sudo apt install -y git git-lfs sqlite3 wget curl ca-certificates openssh-server

Forgejo#

Install#

Install forgejo binary - use wget as normal user, move as root, chown/chmod for git user once in place: Check the forgojo download page instructions for updated binary and GPG signature, etc.

cd /tmp
wget https://codeberg.org/forgejo/forgejo/releases/download/v14.0.3/forgejo-14.0.3-linux-amd64

Verify; key verified the binary matches the signed binary, but key is unknown trust; this is “normal”

gpg --keyserver keys.openpgp.org --recv EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
wget https://codeberg.org/forgejo/forgejo/releases/download/v14.0.3/forgejo-14.0.3-linux-amd64.asc
gpg --verify forgejo-14.0.3-linux-amd64.asc forgejo-14.0.3-linux-amd64

Move it in place, create symlink for upgrade convenience, set correct permissions:

sudo mv forgejo-14.0.3-linux-amd64 /srv/forgejo/forgejo-14.0.3-linux-amd64
sudo ln -s /srv/forgejo/forgejo-14.0.3-linux-amd64 /srv/forgejo/forgejo
sudo chmod +x /srv/forgejo/forgejo-14.0.3-linux-amd64
sudo chown git:git /srv/forgejo/forgejo-14.0.3-linux-amd64
sudo chown -h git:git /srv/forgejo/forgejo

Check it runs#

Check it runs, check version:

sudo -u git /srv/forgejo/forgejo --version

Eg: forgejo version 14.0.3+gitea-1.22.0 (release name 14.0.3) built with GNU Make 4.4.1, go1.25.8 : bindata, timetzdata, sqlite, sqlite_unlock_notify

Initial setup#

Start the web frontend so we can do initial setup:

sudo -u git /srv/forgejo/forgejo web

Go there: http://<HOST>:3000

Config hints:

database:

  • database type: SQLite3 in my case; postgres is overkill, simple file fits my needs better for now
  • path: /srv/forgejo/data/forgejo.db repository root path:
  • overwrote default to match the ZFS structure; “/srv/forgejo/repos” administrator settings:
  • create your administrator username and password

Adjust other settings to your own environment and taste. For now the only defaults I changed here were the title and the slogan.

Install install it#

Click Install button.

Boom! You’re in - you can take a look around if you wish. We should shut down this process and properly configure in systemd as a service. ctrl+c out of the running server.

Setup Forgejo as systemd service#

Create the unit file:#

sudo cat <<EOF > /etc/systemd/system/forgejo.service
[Unit]
Description=Forgejo
After=network.target

[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/srv/forgejo
Environment=USER=git
Environment=HOME=/srv/forgejo
Environment=FORGEJO_WORK_DIR=/srv/forgejo
ExecStart=/srv/forgejo/forgejo web --config /srv/forgejo/custom/conf/app.ini
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/srv/forgejo
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
EOF

Optional SQLite performance tweak#

There are pros and cons, check docs and see if it suits your use case. We can do this now before starting the service.

nano /srv/forgejo/custom/conf/app.ini
[database]
SQLITE_JOURNAL_MODE = WAL

Enable and start the forgejo service#

You killed the ephemeral process we started for the install, right? Ok now enable and start properly:

sudo systemctl daemon-reload
sudo systemctl enable --now forgejo

Configure Forgejo admin user/user(s) and SSH key(s)#

Use Forgejo GUI to add your SSH key and follow the verification steps. NOTE: if the server hosting Forgejo has “AllowUsers” set in /etc/ssh/sshd_config, then:

  • you will need to add ‘git’ user there
  • then systemctl restart sshd

Configure client SSH#

Update your ~/.ssh/config if needed. In my case I already have a Host entry in my config, but now I have two users (and two separate SSH keys):

  • ssh me@tank
  • ssh git@tank

So I must add:

Host tank
    HostName tank
    User git
    IdentityFile ~/.ssh/your-custom-key
    IdentitiesOnly yes

Git, finally!#

Adjust to suit your purposes. My first repo is hugo.git, for creating this website.

Create first repo in Forgejo GUI#

  • Click the ‘+’ icon top right
  • “New repository”
  • Name: “hugo”
  • Ensure “Initialise repository” is unticked
  • Click “Create repository”

Nice.

Git basics#

From client workstation we will init and push for first commit.

Git global user and email#

If you haven’t before set global user and email it will complain, so you can do it now:

git config --global user.name "Your Name"
git config --global user.email "you@domain.com"

init and push#

mkdir ~/hugo
cd ~/hugo
echo "My Hugo personal site" > README.md
git init
git switch -c main
git add README.md
git commit -m "first commit"
git remote add origin ssh://git@tank/niall/hugo.git
git push -u origin main

Forgejo#

Forgejo should now reflect this commit and from here it’s a matter of using and tweaking to your liking.

Create something beautiful.