CentOS 7 was the base for this setup. The goal is simple: run Gogs in Docker, put it behind Nginx, and serve it over HTTPS with a trusted Let's Encrypt certificate. Git over SSH also needs to work on a nonstandard port.

Docker and Nginx

Docker CE installation follows the official CentOS guide: https://docs.docker.com/install/linux/docker-ce/centos

sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce

sudo systemctl enable docker
sudo systemctl start docker

The EPEL package that came with the VPS was too old, which left out packages such as nginx and htop. Installing the Fedora EPEL release fixed that.

yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

Then refresh the package cache and update the system:

yum update

Install Nginx and enable it at boot:

yum install nginx

systemctl enable nginx
systemctl start nginx

Gogs in Docker

The Docker instructions for Gogs are here: https://github.com/gogs/gogs/tree/master/docker

# Pull image from Docker Hub.
$ docker pull gogs/gogs

# Create local directory for volume.
$ mkdir -p /var/gogs

# Use `docker run` for the first time.
$ docker run --name=gogs -p 10022:22 -p 10080:3000 -v /var/gogs:/data gogs/gogs

# Use `docker start` if you have stopped it.
$ docker start gogs

Opening http://IP:10080 redirects to http://IP:10080/install for the initial setup. Without Docker, a normal Gogs install would be reached on http://IP:3000. Here the container maps host port 10080 to container port 3000, so the web UI is on 10080. The same pattern applies to Git over SSH: host port 10022 is mapped to container port 22, so SSH clone URLs use 10022.

If Gogs has already been initialized and you want to start over, delete the files under /var/gogs and refresh the install page. For now, do not run the installer yet; finish the certificate and Nginx setup first.

Getting a Let's Encrypt certificate

Before using acme.sh to issue a Let's Encrypt certificate, point a domain at the server. In this example the site is intended to be browsed at https://gogs.yasking.org rather than http://IP:10080.

acme.sh can validate domain ownership through the Nginx configuration, so http://gogs.yasking.org must already reach this server.

Create /etc/nginx/conf.d/gogs.yasking.org.conf:

server {
    listen       80;
    server_name  gogs.yasking.org;
    root         /usr/share/nginx/html;

    # Load configuration files for the default server block.

    location / {
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

Reload Nginx:

systemctl reload nginx

Visiting http://gogs.yasking.org should now show the default Nginx page. Then request the certificate:

curl https://get.acme.sh | sh

# Re-login to the system before running this
acme.sh --issue --nginx -d gogs.yasking.org

The important output is the success message and the generated files:

[Mon May 28 09:52:57 EDT 2018] Cert success.
...
Your cert is in  /root/.acme.sh/gogs.yasking.org/gogs.yasking.org.cer
Your cert key is in  /root/.acme.sh/gogs.yasking.org/gogs.yasking.org.key

Proxying HTTPS to Gogs

Replace the earlier Nginx config with this one. It terminates TLS and forwards requests to the Gogs container, and it redirects plain HTTP to HTTPS.

server {
    listen 443 ssl http2;
    server_name gogs.yasking.org;
    ssl_certificate /root/.acme.sh/gogs.yasking.org/gogs.yasking.org.cer;
    ssl_certificate_key /root/.acme.sh/gogs.yasking.org/gogs.yasking.org.key;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_protocols TLSv1.2;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
    ssl_prefer_server_ciphers  on;

    location / {
        proxy_set_header  X-Real-IP  $remote_addr;
        proxy_pass http://127.0.0.1:10080$request_uri;
    }
}

# The following block redirects HTTP requests to HTTPS
server {
    listen 80;
    server_name gogs.yasking.org;
    return 301 https://$host$request_uri;
}

Reload Nginx again. Visiting https://gogs.yasking.org/ should show the default Nginx page with a valid certificate.

Configuring Gogs

The Gogs container was started earlier but not configured. With the domain and certificate in place, open the installer.

SQLite3 with the default settings is enough for this setup. The run user can stay as git, since the Docker image already creates that user. Pay attention to three fields: the domain, the SSH port, and the application URL.

Use gogs.yasking.org as the domain, 10022 as the SSH port, and https://gogs.yasking.org/ as the application URL. Gogs uses port 3000 internally by default; Docker exposes it as 10080, and Nginx proxies HTTPS to that port, so users only need to visit https://gogs.yasking.org.

A port scanner can confirm that 10022 is open and correctly bound.

After installation, create a test repository called test-pub. The clone URLs look like this:

HTTPS: https://gogs.yasking.org/yieldone/test-pub.git
SSH:   ssh://[email protected]:10022/yieldone/test-pub.git

HTTPS clone failed with a certificate issuer error:

[root@host ~]# git clone https://gogs.yasking.org/yieldone/test-pub.git
Cloning into 'test-pub'...
fatal: unable to access 'https://gogs.yasking.org/yieldone/test-pub.git/': Peer's Certificate issuer is not recognized.

The workaround was to tell Git not to verify the certificate before cloning:

git config --global http.sslVerify false

For SSH, import the public key through the user settings in the Gogs panel, then test the clone:

[root@host ~]# git clone ssh://[email protected]:10022/yieldone/test-pub.git
Cloning into 'test-pub'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 1), reused 0 (delta 0)
Receiving objects: 100% (8/8), done.
Resolving deltas: 100% (1/1), done.

Notes from the setup

Running a separate SSH server inside the Docker container is not recommended by the Gogs Docker documentation.

You can enter the container with docker exec -it gogs /bin/sh. The git user lives in /home/git. Its authorized_keys file is not a normal list of public keys; it contains command strings. Adding an SSH public key in the Gogs panel creates one of those records.

On the host, the container data is under /var/gogs. The app.ini file under the Gogs data directory can be edited there, followed by docker restart gogs.

Do not disable iptables just to make things easier. It can break Docker port mapping; for example, SSH clone may time out and the port may appear closed.

acme.sh also takes care of certificate renewal after installation, so the Let's Encrypt certificate can keep working without manual reissue.