Take Control of Your Code: Replace GitHub by Self-Hosting Gitea with Traefik as a Reverse Proxy
Published by Weisser Zwerg Blog on
How to set up your own private Git repositories and reclaim your digital independence and privacy.
Rationale
This post is part of the Digital Civil Rights and Privacy series. In a world where privacy and control over your data are increasingly important, this guide will show you how to set up your own private substitute for GitHub using Gitea, paired with Traefik as a reverse proxy. By self-hosting your repositories, you ensure your code stays private, secure, and entirely under your control.
Prerequisites: Networking and Network Topology Overview
In this guide, we’ll use Traefik as a Reverse Proxy on a netcup Virtual Private Server (VPS) that we’ve already set up earlier and made accessible over the internet. To connect this VPS to your home server, where Gitea will be running, we’ll use a WireGuard Hub-and-Spoke (Star) topology. This setup allows us to securely access services like Gitea on your home server from anywhere via the public internet while keeping your data private and under your control.
Here’s a simple visual overview of the setup:
At the top of this configuration at the top of this Ʌ (upside-down V) is your VPS server reachable via the internet, which acts as the “hub” (“star-center”) and hosts the Traefik reverse proxy. Connected to this hub is your home server, where Gitea and other services will run. Both are linked securely using WireGuard in a Virtualized Mesh Network, creating a private and encrypted connection between them.
For a step-by-step guide on setting up a WireGuard virtualized mesh network, check out the WireGuard section in the ODROID-M1: Dockerized Home Assistant page.
Getting my Hub and Spoke setup to work took some time, mainly because I overlooked the following lines in the wg0.conf
at my star center at my netcup VPS:
# Allow routing between clients
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT
As detailed in Wireguard Netzwerk mit Routing einrichten.
In the Traefik docker-compose.yml from my previous post, I still use
traefik:v2.8.0
. If you want to utilize a more recent version, check out Christian Lempa’s boilerplates and specifically his docker-compose/traefik file. Currently, this usestraefik:v3.3.3
.
For this guide, I’ll assume you’re working with a VPS, have Traefik set up as a reverse proxy on this VPS, and that the VPS is accessible via the internet at your-domain.tld
.
Getting Started
The process for setting up a docker compose
instance is well-documented in the Installation with Docker Gitea documentation page, which is an excellent resource to have handy.
A significant portion of the setup below is inspired by Vladimir Mikhalev’ gitea-traefik-letsencrypt-docker-compose repository on GitHub.
I simplified it by removing the integrated backup mechanisms, as I prefer using tools like Borg or Duplicati for backups.
If you’re curious about the full setup, Vladimir’s complete guide is available on his website.
For your convenience, I’ll walk you through the steps here, adding a few extra comments to guide you smoothly along the way.
On your Home Server
Let’s start by setting up the necessary file system structure on your home server to run Gitea.
Make sure to perform these steps as the root
user for proper permissions.
mkdir -p /opt/gitea/config/{postgres/data,gitea/data} && chmod a+w /opt/gitea/config/gitea/data
Next, copy the docker-compose.yaml
and .env
files into the /opt/gitea/
directory.
These files are essential for configuring your Gitea setup:
- The
docker-compose.yaml
file defines the services needed to run Gitea. - The
.env
file stores environment variables for your configuration.
docker-compose.yaml
########################### EXTENSION FIELDS
# Helps eliminate repetition of sections
# Keys common to some of the core services that we always to automatically restart on failure
x-common-keys-core: &common-keys-core
restart: unless-stopped
# docker compose up -d
# docker compose config
name: gitea
services:
postgres:
image: ${GITEA_POSTGRES_IMAGE_TAG}
<<: *common-keys-core
volumes:
- ./config/postgres/data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${GITEA_DB_NAME}
POSTGRES_USER: ${GITEA_DB_USER}
POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD}
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "${GITEA_DB_NAME}", "-U", "${GITEA_DB_USER}" ]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
restart: unless-stopped
gitea:
image: ${GITEA_IMAGE_TAG}
<<: *common-keys-core
ports:
- 3000:3000
- 2222:22
volumes:
- ./config/gitea/data:/${DATA_PATH}
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
GITEA_DATABASE_HOST: postgres
GITEA_DATABASE_NAME: ${GITEA_DB_NAME}
GITEA_DATABASE_USERNAME: ${GITEA_DB_USER}
GITEA_DATABASE_PASSWORD: ${GITEA_DB_PASSWORD}
GITEA_ADMIN_USER: ${GITEA_ADMIN_USERNAME}
GITEA_ADMIN_PASSWORD: ${GITEA_ADMIN_PASSWORD}
GITEA_ADMIN_EMAIL: ${GITEA_ADMIN_EMAIL}
GITEA_RUN_MODE: prod
GITEA_DOMAIN: ${GITEA_HOSTNAME}
GITEA_SSH_DOMAIN: ${GITEA_HOSTNAME}
GITEA_ROOT_URL: ${GITEA_URL}
GITEA_HTTP_PORT: 3000
GITEA_SSH_PORT: ${GITEA_SHELL_SSH_PORT}
GITEA_SSH_LISTEN_PORT: 22
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 90s
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
.env
:
GITEA_POSTGRES_IMAGE_TAG=postgres:17
GITEA_IMAGE_TAG=bitnami/gitea:1.23.1
GITEA_DB_NAME=giteadb
GITEA_DB_USER=giteadbuser
GITEA_DB_PASSWORD=...somesecretpassword.... # `openssl rand -base64 15` or `pwgen -cnys 15 1` or use keepassx "Password Generator" functionality
GITEA_ADMIN_USERNAME=giteaadmin
GITEA_ADMIN_PASSWORD=...somesecretpassword.... # `openssl rand -base64 15` or `pwgen -cnys 15 1` or use keepassx "Password Generator" functionality
GITEA_ADMIN_EMAIL=giteaadmin@gitea.your-domain.tld
GITEA_URL=https://gitea.your-domain.tld
GITEA_HOSTNAME=gitea.your-domain.tld
GITEA_SHELL_SSH_PORT=2222
DATA_PATH=/bitnami/gitea
Now, verify that everything is set up correctly. Run:
docker compose config
The output should confirm that all variables have been successfully replaced with the values from your .env
file.
Finally, you’re ready to start your Docker stack. Run:
docker compose up -d
This command will start all the services in the background, and your Gitea instance will be up and running.
On your Virtual Private Server (VPS)
Let’s continue with extending our set-up of our VPS server. You’ll need to make a few adjustments to your Traefik Reverse Proxy configurations.
Start by updating the configuration in /opt/traefik/docker-compose.yml
. Add or adapt the following settings as needed:
ports:
- "2222:2222"
volumes:
- ./traefik-config/traefik.yml:/etc/traefik/traefik.yml
- ./traefik-config/dynamic.yml:/etc/traefik/dynamic/dynamic.yml
- ./traefik-config/dynamic-tcp.yml:/etc/traefik/dynamic/dynamic-tcp.yml
Next, modify the Traefik configuration file at /opt/traefik/traefik-config/traefik.yml
. Make sure to include or update the following:
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: web-secure
scheme: https
web-secure:
address: ":443"
http2:
maxConcurrentStreams: 250
http3:
advertisedPort: 443
git-ssh:
address: ":2222"
providers:
file:
directory: /etc/traefik/dynamic
watch: true
Then, head over to /opt/traefik/traefik-config/dynamic.yml
and add or adjust the following lines:
http:
middlewares:
compresstraefik:
compress: true
routers:
gitea-router:
rule: "Host(`gitea.your-domain.tld`)"
service: gitea-service
middlewares:
- compresstraefik
tls:
certResolver: default
services:
gitea-service:
loadBalancer:
passHostHeader: true
servers:
- url: "http://10.0.1.5:3000"
Finally, update the TCP configuration in /opt/traefik/traefik-config/dynamic-tcp.yml
. Add or adapt the following:
tcp:
routers:
gitea-ssh-router:
rule: "HostSNI(`*`)"
service: gitea-ssh-service
entrypoints:
- git-ssh
services:
gitea-ssh-service:
loadBalancer:
servers:
- address: "10.0.1.5:2222"
Gitea Configuration
The Gitea configuration file is located at config/gitea/data/custom/conf/app.ini
.
Below are some key settings you may want to adjust to make your self-hosted repository more private and secure.
Here’s a quick overview in diff
format of the changes I’d suggest you might want to consider:
7a8,9
> DEFAULT_PRIVATE=private
> FORCE_PRIVATE=true
78c80
< DISABLE_REGISTRATION=false
---
> DISABLE_REGISTRATION=true
81,82c83,84
< REQUIRE_SIGNIN_VIEW=false
< DEFAULT_KEEP_EMAIL_PRIVATE=false
---
> REQUIRE_SIGNIN_VIEW=true
> DEFAULT_KEEP_EMAIL_PRIVATE=true
DEFAULT_PRIVATE=private
andFORCE_PRIVATE=true
: These settings ensure that all new repositories are private by default and prevent users from creating public repositories, which is ideal for keeping your code secure and private.DISABLE_REGISTRATION=true
: This disables user registration, meaning only you (or users you explicitly create) can access your Gitea instance. This is a good security measure if you’re the only one who needs access.REQUIRE_SIGNIN_VIEW=true
andDEFAULT_KEEP_EMAIL_PRIVATE=true
: These settings require users to sign in before viewing repositories and keep their email addresses private by default, adding an extra layer of security and privacy for your users.
I’d also advise to turn on Multi-factor Authentication (MFA) for your user accounts on Gitea in the > Settings > Security
section.
Enabling MFA on a user does affect how the Git HTTP protocol can be used with the Git CLI. This interface does not support MFA, and trying to use a password normally will no longer be possible whilst MFA is enabled.
Your usual way of dealing with repositories where MFA is enabled is to use SSH Keys
in the > Settings > SSH / GPG Keys
section.
If SSH is not an option for Git operations, an access token can be generated within the “Applications” tab of the user settings page. This access token can be used as if it were a password in order to allow the Git CLI to function over HTTP.
Warning: By its very nature, an access token sidesteps the security benefits of MFA. It must be kept secure and should only be used as a last resort.
For a full list of configuration options, check out the Gitea Configuration Cheat Sheet.
Conclusions
That’s it! You’ve successfully set up your own self-hosted Gitea server with Traefik as a reverse proxy. By taking these steps, you’ve not only created a private and secure space for your code but also taken a significant step toward reclaiming control over your digital privacy and independence.
Now that you’re up and running, think about all the possibilities - hosting your personal projects, collaborating with trusted contributors, or even creating a private repository for sensitive work. You’re in full control of your data, free from third-party dependencies.