The time has come. After months of watching AI companies quietly tweak their terms and AI agents scraping the internet like hungry beasts, I’ve had enough. With a weekend and 3 bonus days off, the motivation and the need are both here. It’s time to spin up a VPS, slap on Nginx, Docker and a Git server - like a true weekend warrior.

You might wonder what brought me to this point in the first place. The answer is pretty simple: AI. You see, for long GitHub was an okay place to host my own repos. Never really had any problem except for the occasional outage. When AI came along though things changed. Suddenly they updated their terms to allow big daddy Microsoft to teach models based on private (at first) and public repos.

First I thought nothing of it. Now things are changing though, the rate at which the models are growing and pulling in all sorts of different material without any kind of reward or even a simple attribution for the original copyright owner’s work led me to realize that getting “off the grid” is the logical next step.

If I can’t control how my code is used, then at least I should control where it lives.

I don’t personally have any repositories (yet) that are worth learning from. When I do have any in the future though, trust me that I want to be ready!

A look at the playing field

There are lots of great providers to choose from. Some I have used before, some are new. So I had to compare them each for my individual use-case. I just wanted something lightweight that lets me push my repos up and let me work in isolation. Privacy and security are the number 1 consideration, otherwise I wouldn’t even be writing this post.

  1. GitLab: Tried and trusted.
  2. Gitea: The familiar kid on the block.
  3. Forgejo: Never heard of this one until now.
  4. Git: Yeah apparently you can just run the repo as a daemon, who knew?

After careful consideration of the pros and cons of each I decided that running plain Git would be too much for me to manage. I would still have to figure the rest of the story for my CI/CD needs, auth, etc. it is way too barebones. GitLab, is getting a bit too “enterprisey” for me personally. If I am about to get off GitHub, why replace it with another one exactly like it. Forgejo is apparently a fork of Gitea, and is run with democratic governance. I personally don’t like politics in my Git provider, so since Gitea and Forgejo is pretty much the same I decided to go with Gitea.

Setting up the server

Nothing too fancy here. I already paid $4 per month for GitHub “Pro” just to host private repos on GitHub Pages. So dropping the same $4 (or even just a bit more) on a Digital Ocean VPS felt like a no-brainer - I can do way more with the VPS than I ever could with a GitHub subscription.

I don’t live in the US, so being able to choose a server location closer to me also brought in the bonus of lower latency. It ain’t much, but as engineers, we have that innate tendency to squeeze out every last bit of juice we can.

Installing Gitea (no Docker, no fuss)

I decided to skip Docker for this one. I have nothing against containers, but I wanted something a bit more simple and transparent. If anything goes sideways, I can easily debug it with plain old system tools.

First, I grabbed the latest Gitea binary for Ubuntu from the official site’s releases. I went with the stable release (for obvious reasons) and dropped it in /user/local/bin:

# change the version to whatever is the latest
wget -O gitea https://dl.gitea.io/gitea/1.23.7/gitea-1.23.7-linux-amd64
chmod +x gitea
mv gitea /usr/local/bin/

Next I created a dedicated git user to keep things clean and secure: (Always do this if you run things on remote servers.)

adduser \
  --system \
  --shell /bin/bash \
  --gecos 'Git Version Control' \
  --group \
  --disabled-password \
  --home /home/git \
  git

Then I set up the directory structure that Gitea expects: (this is all straight from their docs btw):

mkdir -p /var/lib/gitea/{custom,data,log}
chown -R git:git /var/lib/gitea/
chmod -R 750 /var/lib/gitea/

mkdir /etc/gitea
chown root:git /etc/gitea
chmod 770 /etc/gitea

Nearly done. Let’s create a systemd service so Gitea runs a daemon:

tee /etc/systemd/system/gitea.service > /dev/null <<EOF
Description=Gitea
After=network.target

[Service]
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

[Install]
WantedBy=multi-user.target
EOF

Then it was just a matter of starting it up:

systemctl daemon-reexec
systemctl enable --now gitea

Once the service is up and running, Gitea will be listening on port 3000 by default. I navigated to my server’s IP in the browser and walked through the web installer. It sets up SQLite, an admin user and then you are pretty much done.

PS. you have to go through the web installer for the Gitea config to be generated.

Now you can stop here if you want, but there is still more work to be done if we want the experience to be more GitHub-like.

Setting up the reverse proxy and SSL

After pointing a custom subdomain to my server’s IP, it was time to set up the reverse proxy. Gitea runs on port 3000, but no one wants to type :3000 every time they set up a new repo. Plus this is 2025, https is a MUST!

Time to set up Nginx and a free SSL cert from Let’s Encrypt using certbot.

First we need to install the dependencies:

apt update
apt install nginx certbot python3-certbot-nginx

Then, we configure Nginx. I created a basic server block pointing to Gitea on port 3000:

vi /etc/nginx/sites-available/gitea

I used this config (just with my new domain instead of your.domain.com):

server {
    listen 80;
    server_name your.domain.com;

    location / {
        proxy_pass http://localhost: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;
    }
}

Going great so far. Next we need to link it and reload Nginx:

ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Now comes the more interesting part. We add https with Let’s Encrypt:

certbot --nginx -d your.domain.com

This gives us our certificate with the SSL all correctly configured. It even sets up auto renewal. Certbot takes care of all the heavy lifting so we don’t have to mess with any of the config ourselves. Sweet!

Nginx is now handling the https requests and forwarding them to Gitea, which is still happily doing its thing on port 3000.

One last thing. We need to tell Gitea it is running behind a proxy. (don’t remove the other values set under server):

[server]
PROTOCOL = http
DOMAIN = your.domain.com
ROOT_URL = https://your.domain.com/
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000

# Optional, but highly recommended if you don't want anyone to register on your service:
[service]
DISABLE_REGISTRATION = true

Restart Gitea for good measure:

systemctl restart gitea

Boom! - secure, clean and no :3000 in sight. We have our self-hosted Git server with SSL and privacy. Most importantly we no longer have the corporate overlords scraping our commits.

Thanks for following along. If you enjoyed this post, be sure to add my RSS URL at the bottom of the page to your favorite RSS syndicator to get notified when I post anything new.

(^ That’s right, there might a future post on why I prefer RSS for blogging)