Installing Gitea for self-hosted Git service, replacing Gogs

; Date: Wed Mar 11 2020

Tags: Docker »»»» Docker MAMP »»»» Gitea »»»» Gogs

While Gogs is an excellent tool to have a self-hosted Git service (like Github), I recently found out the project is semi-abandoned. A group of Gogs users launched Gitea as a replacement, and in any case it looks like a better server. The goal here is to install Gitea, evaluate it, and see how to convert Gogs-based repositories over to Gitea. The result will be managed in the Docker self-hosting machine I have at home.

Github is an excellent service, allowing anyone to set up a Git code repository. But it's not appropriate for all needs. For example I have code that cannot be published publicly - either it's for a client, or simply something I don't want public. Therefore it's necessary to have a self-hosted Git solution. Gogs was an excellent choice for that, since it is an easy-to-install server providing a Github-like user experience while being self hosted. However like any open source project, when the community forks the project it's something to pay attention to. In this case the issue is Gogs had become semi-stagnant. My evaluation of the Gitea website is the features it supports are a larger superset of what Gogs supports, and therefore I want to switch.

That's the rationale behind this blog post. To try Gitea and see what it takes to convert a Gogs installation to Gitea.

he target platform is an Intel NUC on my desk on which I run several services. Most of the services are managed using Docker. This includes an nginx instance that acts as a HTTP/HTTPS reverse proxy to several services. For the existing architecture, see HTTPS with nginx, using Lets Encrypt, proxying to Gogs and Jenkins back-end services

Converting a Gogs installation to Gitea - no-go for me

The first question was whether it's possible to convert an existing Gogs installation into Gitea. My initial stab at that failed.

Their documentation ( ( says that Gogs users up to version 0.9.146 should have an easy conversion. But - I've upgraded my Gogs instance beyond that version. I tried the instructions on that page, but the conversion did not work.

The primary thing desired from a conversion would be the issue queue. I have issues in only one of the repositories, and it is not important to preserve those issues.

Therefore I've abandoned the idea of converting the Gogs installation to Gitea. It's possible I could have worked it out and I simply wasn't patient enough.

Instead - I'll install Gitea as a fresh install, then one-by-one convert the repositories in my Gogs instance into Gitea repositories. The conversion will be something like:

  • For each Organization create a matching Organization on the Gitea server
  • For each Repository, check out the repository to a local directory, and do this:
    • Create a new repository in the correct Organization on the Gitea server
    • Follow the instructions to push the checked-out repository into the one on the Gitea server
      • git remote rm origin
      • git remote add origin ... URL from Gitea
      • git push -u origin master

Installing Gitea using Docker

Since I have a Portainer instance I first tried doing this via Portainer. While that is pretty successful, I found it better to use a Docker Compose file.

I started with ( the Docker Compose file they provided by the Gitea project.

First step is to create a working directory: mkdir /home/docker/gitea

In my case that directory is a sibling to the other working directories I have for the other services hosted on my NUC.

In that directory create a directory to hold Gitea data: mkdir /home/docker/gitea/gitea-data

Then create a docker-compose.yml file containing:

version: "3.5"

        name: gitea
        driver: bridge

        container_name: gitea
        image: gitea/gitea:latest
            - USER_UID=1000
            - USER_GID=1000
            - SSH_PORT=10222
            - LFS_START_SERVER=false
            - DB_TYPE=sqlite3
        restart: always
            - gitea
            - ./gitea-data:/data
            - ./app.ini:/data/gitea/conf/app.ini
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
            - "3000:3000"
            - '10222:10222'

This creates a named network, gitea, that the container is attached to. If you wanted a MySQL server as a companion, it would be mentioned in this Docker Compose file and it, too, would be attached to the gitea network.

Since an nginx container will sit in front of the Gitea service, it too is also attached to that network using this in its docker-compose.yml:

    external: true

This ensures the Nginx container is attached to the Gitea network.

Hence in nginx.conf it's possible to set up a server instance like:

    server { # simple reverse-proxy
        listen       80;
        location / {
            proxy_pass      http://gitea:3000/;

This works because the hostname gitea is handled by a DNS server built-in to Docker, and the Nginx container is connected to the same network the Gitea container is connected to.

Getting back to the Gitea Docker Compose file - the environment section should actually be presented in the app.ini that we'll go over in a minute. This sets up a few settings, but all those can also be set up in the app.ini.

The volume mounts are similar to what the Gitea project suggests. The first is a data directory in which Gitea will store everything. That data of course needs to be outside the container so that it lives beyond the destruction and recreation of the container. The second mounts the app.ini into the container that I just mentioned. The last two are there because Gitea says they must be there.

Gitea has been instructed to make itself visible on port 3000. Notice that the Nginx reverse proxy connects port 80 to port 3000 on the container.

The last is for SSH support. I'm putting the SSH URL on port 10222 so that it doesn't interfere with normal SSH usage on the server.

It would have been easy to do all this in Portainer as well. It's interesting that I started the installation using Portainer then switched to this Docker Compose file.

Associating a public domain with the resulting server

We have an Nginx instance acting as a reverse proxy. It's possible to associate a public domain so that you can access this server from anywhere. I've done this myself but I don't want to reveal the URL for my server

For example there are dynamic DNS providers that can help associate a DNS entry pointing to your home network. For an example see Remotely access your home network using free dynamic DNS service from DuckDNS

In my case I have a DSL connection that provides a static IP address. Therefore I do not need dynamic DNS support, and can set up a static A address in the configuration for a domain.

In the Nginx file, the full server configuration is:

    server { # simple reverse-proxy
        listen       80;
        access_log   /var/log/git.example.access.log  main;
        error_log   /var/log/git.example.error.log  debug;

        location / {
            proxy_pass      http://gitea:3000/;

In the Gitea configuration we then want to instruct the server what the correct base URL of the site is.

This does not yet handle HTTPS support. I have existing tooling implementing HTTPS in the Nginx container using Lets Encrypt. But I see that Gitea has its own Lets Encrypt support. At the moment it's an open question of whether it's better to implement HTTPS in Nginx or Gitea.

Router/firewall configuration

Most of us have some kind of router/firewall separating our local network from the Internet. In my case I have a DSL line along with a router (DSL modem). It has a firewall built-in allowing me to set up pinholes through the firewall allowing selected services to reach computer(s) on the local network.

For every service I'm hosting at home, there is a domain ( pointing at the static IP address of the home DSL connection.

That means the DSL router firewall is configured to send both port 80 and 443 traffic to the Intel NUC where the Nginx instance is running. Additionally port 10222 is also configured to handle the SSH traffic for Gitea.

Getting the app.ini to modify

We need an app.ini but we need a starting point for one. We don't have an app.ini because the Gitea team doesn't provide a sample file on the website.

What we can do is use docker-compose up to launch the Gitea service once. That will generate a default app.ini that we can use as a starting point.

In other words:

  1. Run docker-compose up
  2. In your browser, connect to http://IP-ADDRESS:3000 and run the Gitea setup
  3. Run docker-compose down to shut down the server
  4. Make a copy of the app.ini it generated so you can modify it
  5. Possibly delete the gitea-data directory generated in this step

Making app.ini settings

The Gitea project has a long list of settings. The app.ini file uses the venerable INI file format.

Most of the app.ini can stay the same. Instead of showing the entire file, I'll go over the parts that were changed:

APP_DATA_PATH    = /data/gitea
SSH_DOMAIN       =
HTTP_PORT        = 3000
ROOT_URL         =
DISABLE_SSH      = false
SSH_PORT         = 10222
LFS_CONTENT_PATH = /data/git/lfs
DOMAIN           =
LFS_JWT_SECRET   = .... generated SECRET
OFFLINE_MODE     = false

The SSH_DOMAIN and SSH_PORT values will be used when Gitea generates the SSH URL for each repository. Likewise the ROOT_URL value is used in generating HTTP URL's.

When you set up HTTPS support the ROOT_URL should use https:// rather than the http:// shown here. I haven't taken that step yet.

The START_SSH_SERVER is set to false to suppress the SSH server built-in to Gitea. Instead it will use an SSH server running inside the container.

I've turned off LFS support because I don't need that feature.

The directory paths (/data/gitea etc) are given relative to the Gitea container. Remember that in docker-compose.yml we mounted a host directory (/home/docker/gitea/gitea-data) as /data in the container.

PATH     = /data/gitea/gitea.db
DB_TYPE  = sqlite3
HOST     = localhost:3306
NAME     = gitea
USER     = root
SSL_MODE = disable
CHARSET  = utf8

The important thing here is the DB_TYPE which I've set to sqlite3. You can set up a MySQL instance if you like. But for my purposes the traffic on this server is extremely light (it's just me) so there's no need for a beefier SQL server. The PATH value is the default provided by Gitea.

Remember that this file path for the database is relative to the Docker container.

You should be able to launch Gitea and get the initial server configuration going, and set up an initial user account for yourself.

Adding your SSH key to Gitea

Gitea (and Gogs) follow practices similar to Github regarding handling SSH keys, and using an SSH key for password-less Git operations.

In my case - a macOS laptop - I have SSH keys already set up in $HOME/.ssh.

Just as in Github - click on Settings in this menu. Then click on the SSH/GPG tab to get to the screen for your SSH key setup.

Just as for Github - click on the Add Key button. Then copy/paste the contents of your $HOME/.ssh/ file.

Ideally that's all that is required to use password-less SSH with Gitea.

Storing a repository in Gitea

To start this, I went into the Gogs server and picked one of the repositories. I cloned that repository onto my laptop.

In Gitea, I created a new repository with a matching name.

Just as happens on Github, the blank repository gives you instructions for setup. Since we have an existing repository, these are the applicable instructions

Pushing an existing repository from the command line
git remote add origin ssh://
git push -u origin master

If you've associated your domain name correctly - then DOMAIN-NAME will be or whatever you set up.

In my case the NUC where this is going has a name nuc.local (because Ubuntu is good about advertising .local domain names). It's possible to use that URL instead. But I want to access this server while away from home, and therefore am using a public domain name, and setting up the firewall trickery to make it all public.

The USER-NAME value is whatever your Gitea user name is, and of course REPOSITORY-NAME is the name for the repository.

I find this sequence of steps to be more useful:

$ git remote -v
$ git remote rm origin
$ git remote add origin ssh://
$ git push -u origin master
Enumerating objects: 71, done.
Counting objects: 100% (71/71), done.
Delta compression using up to 4 threads
Compressing objects: 100% (48/48), done.
Writing objects: 100% (71/71), 847.96 KiB | 33.92 MiB/s, done.
Total 71 (delta 20), reused 71 (delta 20)
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

In case it goes wrong

Instead of that lovely result at the beginning, I got this:

$ git push -u origin master
ssh: connect to host port 10222: Connection refused
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

The Gitea folks have this exact error message shown in their FAQ, along with another related issue that also came up for me. See: (

The exact same error may occur for git clone as well as git push as shown here.

Unfortunately the discussion there - well - I found it too vague and obtuse to be useful. It seems their FAQ is written to accommodate those installing Gitea outside of a Docker container. Their Docker container should be correctly configured out of the box. And, it almost is.

The first key thing was setting START_SSH_SERVER = false. In their documentation it sounds like the built-in SSH server is a better choice. To use that server set this value to true. But I found setting it to false is much better.

The second step is to have Gitea generate an authorized_keys file.

If you carefully follow things - inside the Docker container, the git user has its home directory set as so:

# grep git /etc/passwd
git:x:1000:1000:Linux User,,,:/data/git:/bin/bash

Therefore /data/git/.ssh is where SSH keys are stored so that the SSH daemon can see them. DO NOT ADD KEYS HERE ON YOUR OWN. Instead upload your SSH key using Gitea as shown earlier.

What was needed is an authorized_keys file in that directory. For some reason that file was not present.

On this menu - click on Site Administration.

Then - click on these two buttons.

For me, that was all which was required to get SSH support working correctly.

About the Author(s)

( David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.