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.

So in comes Gitea, a community fork of Gogs, which is written in go-lang and is quite lightweight. More importantly, it can mirror with both Github and Gitlab, giving us data redundancy. Let’s get into it!

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.

980 words — under selfhosting, caddy, docker, gitea