Roll your own git server
Git Logo created by Jason Long
I’ll admit that even in the age of llamas, the things I commit to git are quite valuable to me. I commit code and notes I take. This sort of stuff is very small, and it doesn’t take much storage, but has quite a bit of value to me.
There are so many git remotes out there. To name a few: codeberg, github, gitlab, sourcehut. All of those other, than github, are also open source and can be self-hosted. The issue with using an existing cloud remote though is that they all come with strings attached.
- Github: basically is a database for microsoft to sell your code to openai for training.
- Codeberg: recently changed their policies to broadly ban any LLM-generated content and code built to support LLMs (think Langchain).
- Sourcehut: requires payment and also has content policies.
These platforms have full rights to regulate the content that they host. Many of their policies are in line with the laws of countries that they’re in unsurprisingly, but they do often extend them. For instance, cryptocurrency isn’t illegal, much less cryptocurrency-related repos. Sometimes their policy is also possibly intentionally unclear. For instance, codeberg claims they do not support:
Projects heavily tied to the LLM ecosystem
It seems to me this may cover something like Pydantic-AI, Litellm, or Opencode, even if they were fully human-written. Somewhat confusingly their policy itself only bans projects that are LLM-written, but if their blog suggests otherwise, I wouldn’t rely on the policy not changing.
Whether or not your support these content policies is irrelevant. The point is that platforms, even open source ones, can cut you off at any second without warning.
If you value your git repos as much as I do, then you’ll understand how this is unacceptable.
Git background
Git was created by Linus Torvalds when the version control the linux project was using (bitkeeper) suddenly revoked its free-of-charge license for kernel devs. Also it wasn’t open source to begin with, which made it a controversial choice to use for the open-source linux kernel.
One of the major architectural choices that set git apart from previous version
control systems like subversion is that it’s
decentralized. That is, despite the fact that to most people using git, “origin”
= github.com and git is that tab in the vscode window that pushes to github,
there is no centralization inherent to git. Your ~/Downloads directory is just
as legitimate a remote as git.sr.ht.
We still often like to interact with git repos over a web interface. Git remotes such as codeberg.org and github.com are basically social media for sharing code, but they fundamentally don’t do anything new on the “git” side. A lot of ci-related workflows are built around centralization, but those aren’t part of git either.
For us to run our own git remote, we need a way to push/pull the code + a way to view it on the web, although the second one is kinda optional.
Setting up a git server
We need some sort of computer to start with. You can use a computer you already own (I recommend you do!) or grab a vps from any provider. Here are a few random ones: buyvm, hetzner, digitalocean, ovhcloud, frantech… and many many more. If you’re completely unsure and just want to try something, grab the cheapest available droplet on digitalocean as it’s dead simple to use and has availability.
I recommend using almalinux +10, as the OS. Do a quick dnf update and reboot
before starting.
Throw something like this in your ssh config:
Host oceanside
hostname 123.123.123.123
identitiesonly yes
identityfile ~/.ssh/your-key
kexalgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256
port 23843
user kate
Now on the vps, we’ll make a bare git repo. This is the same as a normal git repo, except it’s basically just the .git folder. The reason for this is that we don’t need a worktree on the remote, and git will complain if you push to a worktree that’s checked out.
mkdir ~/git
cd ~/git
git init --bare -b main llama-sand.git
git init --bare -b main personal-notes.gitThere are two options now. If you have an existing project, you can simply add
the remote. This is what I’d do on my laptop is llama-sand is already cloned
there.
cd ~/codes/llama-sand
git remote -v
origin git@codeberg.org:emiliko/llama-sand.git (fetch)
origin git@codeberg.org:emiliko/llama-sand.git (push)
git remote add droplet oceanside:~/git/llama-sand.git
git push droplet main
git remote -v
droplet oceanside:~/git/llama-sand.git (fetch)
droplet oceanside:~/git/llama-sand.git (push)
origin git@codeberg.org:emiliko/llama-sand.git (fetch)
origin git@codeberg.org:emiliko/llama-sand.git (push)We’ve got two remotes for this repo now! One of them is codeberg, the other is the vps we created! You can git push/pull/fetch from either one, but be sure to keep them in sync. I have an alias on my system to easily view commits across multiple remotes for a repo like this:
alias gitlloga='git log --graph --all --oneline --decorate --color=always | less -R'For the other case, say that personal-notes repo is a new one. In that case you can just git clone it normally:
git clone oceanside:~/git/personal-notes.git
cd personal-notes
git remote -v
origin oceanside:~/git/personal-notes.git (fetch)
origin oceanside:~/git/personal-notes.git (push)And that’s it! We’ve got our own git remote we can pull and push from. The only content policies that bind us are the vps provider’s, and we can easily self-host this to avoid those too.
Web interface
There are two git web interfaces that are popular these days: forgejo and cgit. Click on those links to see examples of either.
Cgit is extremely fast and light on resources. It’s a great choice if your vps is small. I’d say you need to have a minimum of 2gb of ram dedicated to forgejo to consider it, otherwise cgit is a better bet. Forgejo’s advantages are git lfs support and a much more modern interface.
I run both, cgit on a cheap droplet and forgejo on a self-hosted computer.
Forgejo
If you’re looking to set this up, check out my podman + anubis blog, which walks through how to get those quadlets running. It’s the same idea. Just use a container image from here.
If you don’t need public access to it, then you can skip the anubis step. My
forgejo.container looks like:
[Unit]
Description=Forgejo container
After=local-fs.target
[Container]
Image=codeberg.org/forgejo/forgejo:11.0.2
ContainerName=forgejo
# Environment=USER_UID=1001
# Environment=USER_GID=1001
PublishPort=9123:3000/tcp
PublishPort=9122:22/tcp
Volume=/home/podman/containers/forgejo/data/forgejo:/data:z
Volume=/etc/localtime:/etc/localtime:ro
[Install]
WantedBy=default.targetI bindmount the entire forgejo data directory, but podman volumes are a good approach as well. I just don’t trust myself to not prune those by accident.
You can then follow the restic backup blog to ensure you
don’t lose your data. The only things you’ll need to adjust are the paths in
/etc/restic/include.txt.
Cgit
Ingredients
Prerequisites:
- A smol vps (in for this tutorial it’ll be running almalinux 10).
- git (cli tool)
- Bare git repos under /home/kate/git
What we’ll get:
- Caddy
- cgit, fcgiwrap, python markdown stuff
- restic (this is later)
Below I assume almalinux 10, which is rhel 10 compatible. I also assume a user
kate and that the git repos are stored under /home/kate/git.
Cgit setup
Start by installing things we need. The first line is rhel specific, debian-based stuff won’t need it:
sudo dnf install epel-release bash-completion
sudo dnf install cgit fcgiwrap python3-markdown python3-pygments restic
# Check where it is, should be in /var/www/cgi-bin/cgit, otherwise adjust steps
rpm -ql cgit | grep '/cgi-bin/cgit$'Paste something along the lines of the following into /etc/cgitrc. If you want
to change the logo/favicon, add them to /usr/share/cgit and then update the
names. You may also need to update the Caddyfile later.
root-title=Oceanside
root-desc=Git repositories
css=/cgit.css
logo=/cgit.png
favicon=/favicon.ico
virtual-root=/
remove-suffix=1
enable-http-clone=0
enable-index-owner=0
enable-git-config=1
enable-commit-graph=1
about-filter=/usr/libexec/cgit/filters/about-formatting.sh
root-readme=/var/www/cgit-about.md
readme=:README.md
readme=:readme.md
readme=:README.txt
readme=:readme.txt
readme=:README
# Disabling cache is easiest for a small vps
cache-size=0
# Must be last
scan-path=/home/kate/git
We need fcgiwrap since we’re using caddy instead of lighttpd. Every other guide would suggest lighttpd. However, caddy is so much better as a reverse proxy that it’s easily worth one extra package to use it.
Start up fcgiwrap as a systemd service:
sudo mkdir /etc/systemd/system/fcgiwrap@kate.socket.d
sudo tee /etc/systemd/system/fcgiwrap@kate.socket.d/override.conf >/dev/null <<'EOF'
[Socket]
SocketGroup=caddy
SocketMode=0660
EOF
sudo systemctl enable --now fcgiwrap@kate.socketNow we just need caddy and we’ll be good to go. Your caddyfile should have an entry similar to this:
cgit.mami2.moe {
encode zstd gzip
route {
@not_netbird {
not remote_ip 10.0.0.0/24
}
respond @not_netbird 404
@assets path /cgit.css /cgit.png /favicon.ico /robots.txt
handle @assets {
root * /usr/share/cgit
file_server
}
handle {
reverse_proxy unix//run/fcgiwrap/fcgiwrap-kate.sock {
transport fastcgi {
env SCRIPT_FILENAME /var/www/cgi-bin/cgit
env CGIT_CONFIG /etc/cgitrc
env PATH_INFO {path}
capture_stderr
}
}
}
}
}Now you may notice I have a @not_netbird handler. This is since I don’t
actually want anyone to access my cgit, other than myself. I do this by making
caddy respond with a 404 when anyone outside my netbird vpn requests content.
Caddy will still be able to obtain ssl this way, as long as your public dns
records point this vps.
If you’re okay with others seeing your cgit, you can remove the
respond @not_netbird 404 line.
Now start or reload caddy and your cgit should be up!
sudo systemctl enable --now caddy.service
# Or
sudo systemctl reload caddy.service
Restic Backup
Make sure you back up your files! A vps is a finicky thing, and in any case you need multiple backups for important files like your git repos.
Check out my restic backups blog, which was initially a section here!