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 Sitebuild.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.
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. ↩︎