Howto: Building the Site With Drone

Published on

Everything that I needed to do to install and configure Drone.io in order to set up a simple CI/CD pipeline to rebuild and (eventaully) redeploy my website was documented somewhere on the Internet. I just had to piece the puzzle together from the information I found scattered among multiple sites. This post is my attempt to get everything documented in one place. I'm sure future-me will need the reminder. Hopefully, this information may be of some help to others as well.

DNS Name and VPS Hosting

I've been a GoDaddy customer for many years now, as the registrar of a domain main that I use for my immediate family's e-mail, web-hosting, etc needs. I haven't had any problems with them. I could have used them for the new domain I am setting up for personal projects, but I wanted to get some experience with other registrars. I ended up chosing [NameCheap}(https://www.namecheap.com/) to acquire a .us domain name. As their company name suggests, their prices are low. So far, so good with them as well.

My 1GB Digital Ocean droplet is pretty much maxed out running my Gitea server along with some other projects. I knew I would need another VPS to use as a Web Server and Drone.io build machine. Since I was able to get a $20 credit for Linode by using an affiliate link of one of my current favorite YouTube creators, I decided to give them a try1.

Docker Compose

Since I have experience using the linuxserver/letsencrypt container to set up HTTPS reverse proxies to a number of projects, I decided to stick with that approach. My new Linode server currently has:

  • Docker 19.03.5
  • Docker-Compose 1.21.0

This combination supports docker-compose files up to version 3.6, which in particular allow me to set up a named network and to use named volumes from within the docker-compose.yml file.

Network


version: "3.6"

networks:
  proxy:
    external: false

Named Volumes


volumes:
  letsencrypt:
  drone:

LetsEncrypt Web Server and Reverse Proxy

I will be using the Nginx reverse-proxy deployed as part of the linuxserver/letsencrypt container to route:

  • www.example.org - to my Web Site
  • build.example.org - to my Drone.io build server

services:
  letsencrypt:
    image: linuxserver/letsencrypt
    container_name: letsencrypt
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=US/Boston
      - URL=example.org
      - SUBDOMAINS=www,build
      - VALIDATION=http
      - EMAIL=mail@example.org
    volumes:
      - letsencrypt:/config
      - ~/www:/config/www
    networks:
      - proxy
    ports:
      - 443:443
      - 80:80
    restart: unless-stopped

The ~/www:/config/www volume allows me to serve my site by copying the build files into my ~/www folder.

Both port 80 and port 443 are bound to the host, to allow Let's Encrypt to do its magic. The default nginx configuration of the linuxserver/letsencrypt image automatically redirects HTTP access to HTTPS.

Drone Server

Since I plan to use Drone.io with my Gitea server, I started with the how to install the Drone server for Gitea at Drone.io's main documentation site. Their documentation for setting up an OAuth Application and a shared secret was very straightforward. The only thing I needed to do was to translate their docker cli example into the necessary docker-compose service configuration.


  drone-server:
    image: drone/drone:1
    container_name: drone-server
    environment:
      - DRONE_AGENTS_ENABLED=true
      - DRONE_GITEA_SERVER=gitea-server.example.org
      - DRONE_GITEA_CLIENT_ID=oauth-client-id
      - DRONE_GITEA_CLIENT_SECRET=oauth-client-secret
      - DRONE_RPC_SECRET=shared-secret
      - DRONE_SERVER_HOST=build-server.example.org
      - DRONE_SERVER_PROTO=https
    volumes:
      - drone:/data
    networks:
      - proxy
    expose:
      - "80"
      - "443"
    restart: unless-stopped
    depends_on:
      - letsencrypt

Note in particular the use of expose rather than port. I do not want to provide access to the Drone server from the Internet-at-large. The only access should be through the nginx reverse proxy, which is using the proxy network defined above. The expose configuration allows the containers using that proxy to access the ports listed, without further exposing them to the host.

Drone Runner

Setting up a Docker-based Drone Runner was also very straightfoward. Again translating docker cli to docker-compose, the service configuration is:


  drone-runner:
    image: drone/drone-runner-docker:1
    container_name: drone-runner
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=build-server.example.org
      - DRONE_RPC_SECRET=shared-secret
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=build-server.example.org
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - proxy
    expose:
      - "3000"
    restart: unless-stopped
    depends_on:
      - letsencrypt
      - drone-server

Activating Repositories

While following along with the Drone.io documentation, I kept waiting for where they would explain how to set up authentication for the Drone Server. For some reason, I expected to have to create users and assign them passwords. I never found such documentation, because one doesn't set up users within the Drone Server. Everything is controlled by the OAuth configuration that was set up earlier.

Connecting to my new https://build.example.org site immediately redirected me to my Gitea server to authenticate. Once that was done, all of my user's repositories show up in Drone's interface where I can selectively activate Drone to work with only the repositories I wish to automatically build.

Drone Pipeline

The actual build pipeline for a Docker Runner is set up as a .drone.yml file contained within the source code of the repository to be built. A great place to get started is Drone.io's Configuration Overview.

Since it is never a good idea to include passwords or other sensitive information in a Git repository, Drone supports Secrets. I chose to use several per-repository secrets in my pipline, below.

I started out by using the cbrgm/drone-hugo plugin. The example pipeline worked almost out of the box. However, the examples assume that the Hugo Theme being used is directly part of ones repository code. This is not the case for my site – I include my theme as a git submodule.

It took some digging around, but I eventually found how the Cloning page showed using the alpine/git container to run arbitrary git commands. Additional searching of the Internet led me to Sing's Log : GIT submodule note, which reminded me that I would need to both init and update the submodule.

To publish the results of building my Hugo site, I chose to use the appleboy/drone-scp plugin to copy the contents of the public/ folder to my web server. This is where the Drone Secrets became incredibly useful. I created a separate SSH key to be used exclusively by the build pipeline. The public key was added to the authorized_keys of my web server, and the private key was pasted into the Drone Secret configuration interface.

At this point, I was able to build and publish my web-site automatically, simply by pushing a change to the master branch of my Gitea repository. There was just one more thing I wanted to do, and that was to minify the output. Unfortunately, the cbrgm/drone-hugo plugin does not yet support the --minify parameter for Hugo's build command. I was tempted to fork the https://github.com/drone-plugins/drone-hugo repository to add the support, but reading through some of the issue discussions there led me to https://github.com/hypervtechnics/drone-hugo instead, which does provide support for minify.

My current .drone.yml file is below:


kind: pipeline
name: default

steps:
  - name: submodules
    image: alpine/git
    commands:
      - git submodule init
      - git submodule update --recursive

  # Note: the plugin/hugo plugin does not currently support Hugo's minify 
  # option. However, the alternative hypervtechnics/drone-hugo plugin does.
  # Until there is a reason to change back to plugin/hugo, use the alternate.
  - name: build
    image: hypervtechnics/drone-hugo
    settings:
      hugo_version: 0.62.2
      validate: true
      minify: true
      config: ./config.toml

  - name: publish
    image: appleboy/drone-scp
    settings:
      host:
        from_secret: publish_host
      user:
        from_secret: publish_user
      key:
        from_secret: publish_key
      target:
        from_secret: publish_target
      source: public/*
      strip_components: 1
    when:
      branch:
        - master
      event:
        exclude:
          - pull_request


This article, including the code examples, is licensed under a Creative Commons Attribution 4.0 (CC BY 4.0) License.


  1. Linode's prices for a 1GB server is identical to Digital Ocean's, and the storage and bandwidth specs are the same as well. However, I was very pleasantly surprised to see that my Linode server was automatically set up with some memory swap space, which my Digital Ocean server doesn't have. For small projects like mine, it's looking like Linode might be the better choice. ↩︎