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.
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.
version: "3.6" networks: proxy: external: false
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 - EMAILemail@example.com volumes: - letsencrypt:/config - ~/www:/config/www networks: - proxy ports: - 443:443 - 80:80 restart: unless-stopped
~/www:/config/www volume allows me to serve my site by copying the build files into my
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.
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.
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
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.
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
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
.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. ↩︎