Introduction to Gitea
Our Jumphost-VM is running and secured with a VPN and firewall - nice. But what are we going to do with it now? At the end we want to run some services like a webserver on it. But the jumphost should provide basic infrastructure necessary therefore. So in this article we're going to find a way to host our own Git service and use actions to provision and configure out actual target machine.
Gitea is a perfect choice for a self-hosted Git service. We don't want to depend on any other 3rd party and their pricing models to host our own repositories and execute actions. GitHub, GitLab or Bitbucket among others are also available but not really open source. Gitea offers a great integration of CI/CD tools and their Gitea Actions are compatible with GitHub Actions which makes it very versatile and easy to switch workflows from latter to former.
Gitea and Docker Compose
Gitea can be easily installed with using Docker containers and a Docker Compose file to handle settings and multiple containers at the same time (necessary later) official documentation. A valid minimum example to run it on your own machine looks like this:
services:
gitea:
image: gitea/gitea:latest
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=git.paulelser.com
- GITEA__server__PROTOCOL=http
- GITEA__server__ROOT_URL=https://git.paulelser.com/
- GITEA__server__HTTP_PORT=3000
volumes:
- ./gitea:/data
networks:
- gitea_network
ports:
- 3000:3000
networks:
gitea_network:
name: gitea_network
Remember that I have set a DNS entry for git.paulelser.com
to the internal IP address of my VM. However, this setup allows us only to access a HTTP version of Gitea on port 3000 which needs to be accessed by http://git.paulelser.com:3000
. To establish HTTPS and necessary TLS certifactes, we have to incorporate a certificate authority (CA) and do port mapping. In the following we are using Let's Encrypt as the CA and Traefik as a reverse proxy that handles the port mapping for us.
Let's Encrypt Certificate Creation
Let's start by saying that you can handle the certificate creation dynamically in your Docker Compose file. I tried it for a long time and failed since my domain registrar Squarespace doesn't allow dynamic access and I couldn't get interactive DNS-Challenges working - if you do know how, let me know 😉 Your specific registrar might handle the process differently and also allows a dynamic certificate creation. Anyway, Let's Encrypt (LE) is a CA that is trusted by modern browsers. LE uses the Automatic Certificate Management Environment (ACME) to verify that somebody owns and controls a domain before issuing a certificate. Therefore a specific file needs to be placed on the web server or (as it was in our case) a specific DNS record needs to be created. The DNS record contains a string created by the ACME. Using LE certificates ensures that the traffic itself is encrypted but it doesn't authenticate the server you are communicating with. That means that your connection might not be intercepted by a third party but somebody could have possibly taken over the server itself and it is hostile now. There are other forms of Extended Validation (EV) or Organization Validated (OV) certificates that proof by other means that the server you are communicating to belongs to a real organization. My LE certificate only requires a mail address and that I actually have access to the domain and the registrar's website.
Let's first install certbot
(with sudo apt install certbot
) and then create a bash file get_cert.sh
:
#!/bin/bash
# Make sure certbot is installed
# If not, run 'sudo apt-get install certbot'
# Replace this with your email address
EMAIL="me@paulelser.com"
# Replace this with your domain name
DOMAIN="git.paulelser.com"
# Generate the certificate
sudo certbot certonly --manual \
--preferred-challenges=dns \
--email $EMAIL \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos \
-d $DOMAIN
# Copy the certificates to the correct directory for Traefik
sudo mkdir -p /etc/traefik/certs
sudo cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/traefik/certs/
sudo cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /etc/traefik/certs/
# Set the correct permissions
sudo chown -R $(whoami):$(whoami) /etc/traefik/certs
sudo chmod 600 /etc/traefik/certs/*
echo "Certificates have been generated and prepared for Traefik."
This script is interactive and requires you to copy a string and enter it in your DNS settings like here:
When successful, your private key privkey.pem
and the corresponding certificate fullchain.pem
will be created in /etc/traefik/certs
. Note that during the process you can use a website like https://dnschecker.org/#TXT/_acme-challenge.git.paulelser.com to check the propagation of your DNS settings through all DNS servers in the world. Kinda cool to look at!
You will need some additional file certificates.toml
with information about the location of the certificate:
[[tls.certificates]]
certFile = "/certs/fullchain.pem"
keyFile = "/certs/privkey.pem"
[tls.stores]
[tls.stores.default]
[tls.stores.default.defaultCertificate]
certFile = "/certs/fullchain.pem"
keyFile = "/certs/privkey.pem"
Traefik Setup
Before coming to the final version of the Docker Compose file, we need to discuss Traefik. Traefik is a reverse proxy and can be used as a load balancer aswell. It fits well with microservices and routes incoming requests to the appropriate backend services (like Docker). In our case it also handles HTTPS connections and certificate management and also does the port mapping. There are other options like Nginx or HAProxy but for our uses Traefik suffices.
Traefik requires some flags and settings to be able to handle HTTPS connections. The specific port-mapping is shown here:
--entrypoints.web.http.redirections.entrypoint.to=websecure
redirects every HTTP request to the address that is specified in --entrypoints.websecure.address=:445
. Note that for convenience it makes sense to not use the port 445 internally, but the default port 443 for HTTPS traffic. Now this internal port is mapped to a port of the host system by the section ports
of the service traefik
in this Docker Compose file. It looks like host-port:docker-port
. If you use 443 as the host port, you can now access Gitea on your given port domain without specifying the port number, e.g: git.paulelser.com
. If you were to use another port on the host like 444 (again, which is not advisable) then you would have to type git.paulelser.com:444
in the browser since it is not the default for HTTPS connections.
You'll see that only port 443 is used in the production code for the sake of convenience. Also you will notice that there is HTTP 308 error code (Permanent Redirect) in case you try to access http://git.paulelser.com
. The server will accept this request but forces it to use HTTPS instead of the unencrypted HTTP.
Finally it is time for the full Docker Compose file:
services:
gitea:
image: gitea/gitea:latest
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=git.paulelser.com
- GITEA__server__PROTOCOL=http
- GITEA__server__ROOT_URL=https://git.paulelser.com/
- GITEA__server__HTTP_PORT=3000
volumes:
- ./gitea:/data
networks:
- gitea_network
labels:
- traefik.enable=true
- traefik.http.routers.gitea.rule=Host(`git.paulelser.com`)
- traefik.http.routers.gitea.entrypoints=websecure
- traefik.http.routers.gitea.tls=true
- traefik.http.services.gitea.loadbalancer.server.port=3000
traefik:
image: traefik:v2.9
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --providers.file.directory=/etc/traefik/
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./certificates.toml:/etc/traefik/certificates.toml:ro
- /etc/traefik/certs:/certs:ro
networks:
- gitea_network
networks:
gitea_network:
name: gitea_network
After running it and spinning up the Gitea and Traefik containers in the same Docker network, you have to visit git.paulelser.com
(or your own domain) and click through an installation of Gitea. I opted for a SQLite3
database since it is very memory-efficient and I don't have high requirements for the databse of my repositories that might need a MySQL
or PostgreSQL
database. Set up an administrator account and then we have our own Git server running on our own VM. Clean! 😎