Choosing a Source Control System and Host

Over the last 10 years I've used a handful of source control systems, however the world seems to mostly have converged on git as the source control system of choice for open source projects. It's also the source control system that I'm most familiar with, so it's the system I'll focus on.

With that decided, let's focus on the options for self hosting a git repository. Gitlab CE is what I run on the local home-lab, however it's a little heavy for what I want to run outside the home. So, that leaves Gogs, Gitea, Phabicator, cgit, or gitbucket. There are other alternatives out there, but these are the ones I evaluated.

Of the systems I looked at above, I decided on giving Gitea a try. Hosting it is fairly straightforward as the application is a single binary. With that said I run as many of my services in containers as I can. Running Gitea is a docker container is fairly straightforward.

The docker-compose.yml for the service looks like this.

version: "3"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:1.10
    environment:
      - USER_UID=${USER_UID}
      - USER_GID=${USER_GID}
      - DB_TYPE=postgres
      - DB_HOST=db:5432
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWD=${DB_PASSWORD}
    restart: always
    networks:
      - gitea
    volumes:
      - ${GITEA_DATA_DIRECTORY}:/data:Z
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "${GITEA_HTTP_PORT}:3000"
      - "${GITEA_SSH_PORT}:22"
    depends_on:
      - db
  db:
    image: postgres:12-alpine
    restart: always
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    networks:
      - gitea
    volumes:
        - ${DB_DATA_DIRECTORY}:/var/lib/postgresql/data:Z
  smtp:
    image: docker.io/namshi/smtp
    restart: unless-stopped
    environment:
      - SMARTHOST_ADDRESS=${SMTP_HOST_ADDRESS}
      - SMARTHOST_PORT=${SMTP_HOST_PORT}
      - SMARTHOST_USER=${SMTP_HOST_USER}
      - SMARTHOST_PASSWORD=${SMTP_HOST_PASSWORD}
      - SMARTHOST_ALIASES=${SMTP_HOST_ALIASES}
    networks:
      - gitea

There's an accompanying .env file for the variables above. Running the service is as simple as docker-compose pull && docker-compose down && docker-compose up -d

The only thing remaining is to expose the service to the world through a reverse proxy. I personally use nginx for most of my simple reverse proxying needs. Thaat config looks a little like this.

upstream git_backend {
        server 127.0.0.1:<locally exposed port>;
}

server {
        listen <server ip>:80;
        server_name <server name>;

        location ^~ /.well-known/acme-challenge/ {
                # Set correct content type. According to this:
                # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
                # Current specification requires "text/plain" or no content header at all.
                # It seems that "text/plain" is a safe option.
                default_type "text/plain";

                # This directory must be the same as in /etc/letsencrypt/cli.ini
                # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
                # there to "webroot".
                # Do NOT use alias, use root! Target directory is located here:
                # /var/www/<server name>/.well-known/acme-challenge/
                root /var/www/<server name>;
        }

        location / { 
                return 301 https://$host$request_uri; 
        }
}

server {
        listen <server ip>:443 ssl http2;
        server_name <server name>;
        server_tokens off; ## Don't show the nginx version number, a security best practice

        ssl on;
        ssl_certificate /etc/letsencrypt/live/<server name>/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/<server name>/privkey.pem;

        ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 5m;

        add_header Strict-Transport-Security max-age=31536000;
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Robots-Tag none;
        add_header X-Download-Options noopen;
        add_header X-Permitted-Cross-Domain-Policies none;

        error_log  /var/log/nginx/<server name>_error.log  warn;
        access_log  /var/log/nginx/<server name>_access.log;

        client_max_body_size 10M;

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_pass http://git_backend;
        }
}

With this we expose the http endpoint for an ACME certificate provider, and redirect all other http traffic to the https endpoint. Nginx terminates the SSL connection and passes the traffic to the local backend. This is the general setup for most of my simple https termination and reverse proxying.

With these two configurations in place, Gitea can be brought up and the service can be accessed publicly on the server.

Gitea Main Page