Selfhosting git with Gitea, Docker, Caddy
Published:
Something I’ve been doing recently is starting to self-host as much as I can. I don’t like relying on businesses, since they can change their services on a whim. I host an IRC network bouncer, a feed reader, and so on. But why not a git frontend?
It also doesn’t make sense to me that Github isn’t open-source, despite being a company built on git—libre software that powers the programming industry. Gitlab is commendable for actually being open source[[1]], but it’s fairly heavy in resource usage and it has a lot of features I don’t really need.
Prerequisites
docker
and docker-compose
need to be installed on your system. In
this case, I’m running it with a bunch of other services on a
hetzner.de Cloud VPS for €5/month. You could
probably run it on their €2.50/month plan. (2GB vs 4GB of RAM)
Docker-Compose: Part 1
In this case, both Gogs and Gitea provide premade docker images for us.
Handy! Thankfully they’re mindful about docker image size, with
Gitea 1.4 clocking in at a
mere 34MB
.
This means all we have to do is create a docker-compose
file:
version: "3.3"
services:
git:
image: gitea/gitea:latest
ports:
- "3000"
volumes:
- type: bind
source: ./data/git
target: /data
restart: always
This file defines git
as a service, using the mentioned gitea image.
You can use latest
or keep it at a specific version, it’s up to you.
Next, Gitea runs on port 3000
by default, so we need to expose the
docker container’s port likewise. Note that we are not doing
3000:3000
, which would have the port exposed externally. Instead, we
have a tool that’ll handle the routing for us.
Caddy
Caddy is an HTTP/2
webserver. Its killer
feature is automatically handling HTTPS with
Let’s Encrypt. Seriously, I cannot recommend it enough for personal
projects. Gone are the days of manually handling certificates and using
nginx. [2]
Caddy’s Caddyfile
syntax is fairly simple:
git.andrewzah.com {
tls [email protected]
log / stdout {combined}
errors stderr
proxy / http://git:3000
}
Here, we’re giving instructions for the domain git.andrewzah.com
. The
TLS
directive configures HTTPS connections, and providing an email
means Caddy won’t prompt us.
The log
and errors
directives are self explanatory.
proxy
is what we’re really interested in. It lets us pass the
connection to the local gitea container we just created, on port 3000
.
Docker-Compose: Part 2
In order to run Caddy, we need to add it as a service:
services:
http:
build:
context: github.com/abiosoft/caddy-docker.git
args:
version: 0.10.11
volumes:
- type: bind
source: ./data/sites/
target: /var/www/sites/
- type: bind
source: ./data/caddypath/
target: /var/caddy/
- type: bind
source: ./Caddyfile
target: /etc/Caddyfile
environment:
CADDYPATH: "/var/caddy"
env_file: ./secret.env
ports:
- "xxx.xxx.xxx.xxx:443:443"
- "xxx.xxx.xxx.xxx:80:80"
restart: always
Again, a docker image is provided for us thanks to abiosoft
. We can
specify which caddy version and any caddy plugins if desired. Since
we’re not running a dns or net server, we don’t have to worry about much
configuration.
data/sites/
and /data/caddypath
store information related to Caddy
and the automatically generated Let’s Encrypt files. /etc/Caddyfile
is
where Caddy looks, so we bind our caddyfile there.
env_file: ./secret.env
is only needed if a caddy plugin requires
sensitive data, such as the tls.dns.gandiv5
plugin.
Replace the port IP with your server’s IP. The reason we use that is
because by default, yyy:zzz
will listen on all interfaces.
Docker-Compose: Part 3
You may have noticed by now that we’re missing one big thing… a database. This one is simple, because Gitea is smart and runs on Postgres:
services:
git_db:
image: postgres:9.6.7-alpine
env_file:
- ./secret.env
restart: always
volumes:
- type: bind
source: ./data/postgres/
target: /var/lib/postgresql/data/
In secret.env
, you’ll need to set the following:
POSTGRES_USER=xxxx POSTGRES_PASSWORD=yyyyyy POSTGRES_DB=git
Now that we have a database service, we can add this to the gitea
service:
services:
git:
depends_on:
- git_db
Editing SSHD
There’s one problem now, which is that if you actually try to run this
configuration, you’ll be refused. We never actually exposed ssh’s
default port 22
, nor did we start listening to it!
So let’s listen to 22
for git, and 2223
for regular ssh. Edit, with
sudo permissions, /etc/ssh/sshd_config
:
# What ports, IPs and protocols we listen for Port 2223 Port 22
At this time, I would also recommend disabling password login, as one
can never have enough security. Just make sure you add your
ssh
key output to ~/.ssh/authorized_keys
! You’ll be locked out otherwise.
PermitRootLogin without-password PasswordAuthentication no
Docker-Compose: Final
Lastly, now that we’re listening on port 22
, add this to the
docker-compose.yml
:
services:
git:
image: gitea/gitea:latest
ports:
- "22:22"
Now when we do git push origin master
, with origin
set to
https://git.andrewzah.com/…;
, it’ll reach the gitea container!
In total, it should look like this:
version: "3.3"
services:
git:
image: gitea/gitea:latest
ports:
- "3000"
- "22:22"
volumes:
- type: bind
source: ./data/git
target: /data
restart: always
git_db:
image: postgres:9.6.7-alpine
env_file:
- ./secret.env"
restart: always
volumes:
- type: bind
source: ./data/postgres/
target: /var/lib/postgresql/data/
http:
build:
context: github.com/abiosoft/caddy-docker.git
args:
version: 0.10.12
volumes:
- type: bind
source: ./data/sites/
target: /var/www/sites/
- type: bind
source: ./data/caddypath/
target: /var/caddy/
- type: bind
source: ./Caddyfile
target: /etc/Caddyfile
environment:
CADDYPATH: "/var/caddy"
env_file: ./secret.env
ports:
- "xxx.xxx.xxx.xxx:443:443"
- "xxx.xxx.xxx.xxx:80:80"
restart: always
Conclusion
If everything was done correctly, you should now have a self-hosted git frontend. Nice!
I recommend checking out awesome-selfhosted to see a huge list of other software you can host. reddit.com/r/selfhosted is also a good resource. The possibilities are endless… You could host a Kanban board or a Magic: The Gathering Cockatrice server!
If you start hosting multiple services, it makes more sense to break
them out into separate folders, with http
being its own central
service. You can check out my
services repository to
get an idea.