Easier Drone Deploys with Docker Images

Custom Docker images for Drone CI can reduce redundancy and simplify setup of new deployments. We'll look at incrementally building custom images for deploys, and how that affects our Drone configuration.

Drone CI configuration files have a straightforward syntax similar to that of a docker-compose.yml file: you specify different build stages, pick an image for each stage, and run commands against that image:

---
pipeline:
  build:
    image: golang
    commands:
      - go get
      - go build
      - go test

Drone also supports secrets, which keep sensitive information like SSH keys and API tokens out of your (possibly public) configuration file. A deploy step might look for credentials in environment variables, store those credentials in a file, and perform commands using those credentials:

---
pipeline:
  deploy:
    image: alpine
    secrets: [ ssh_private_key, ssh_host_key ]
    commands:
      - mkdir "$${HOME}/.ssh"
      - echo -n "$${SSH_PRIVATE_KEY}" > "$${HOME}/.ssh/id_rsa"
      - chmod 700 "$${HOME}/.ssh/id_rsa"
      - echo "$${SSH_HOST_KEY}" >> "$${HOME}/.ssh/known_hosts"
      - scp -r ./output user@deploy.example.com:/var/www/html

This is prone to repetition. For each new repository (at least without the paid global secrets feature), you have to inject secrets which may be the same as other projects in your CI environment, and perform the same setup steps to expose those secrets to shell commands.

Building custom deploy images

Instead of all this repetition, let's build our secrets into images we control. We'll start with a generic image, drone-rsync-ssh:

FROM alpine:3.7
COPY drone-ssh-keys.sh /usr/bin/drone-ssh-keys
RUN apk add --no-cache openssh-client rsync

The mkdir/echo/chmod behavior from our verbose Drone config file is wrapped up in drone-ssh-keys.sh, for easy calling in the future. Here's how .drone.yml changes under this image:

---
pipeline:
  deploy:
    image: drone-rsync-ssh
    secrets: [ ssh_private_key, ssh_host_key ]
    commands:
      - drone-ssh-keys
      - scp -r ./output user@deploy.example.com:/var/www/html

We've already cleaned up this build stage, but there's still redundancy in adding secrets to the Drone repository. Plus, if our secrets change, we have to update every repository that uses them.

We can go a step further if we have access to private Docker images. Let's bundle those credentials right in our image with a new Dockerfile:

FROM alpine:3.7
COPY deploy-assets /deploy-assets
RUN apk add --no-cache openssh-client rsync && \
  mkdir /root/.ssh && \
  cd /deploy-assets && \
  cp deploy.key deploy.pub known_hosts config /root/.ssh && \
  chmod 0600 /root/.ssh/deploy.key

The deploy-assets directory contains everything we need to run deploys, including secrets and file copying utilities. Our new .drone.yml is very compact, and we can call our commands without any additional setup:

---
pipeline:
  deploy:
    image: drone-rsync-ssh-secrets
    commands:
      - scp -r ./output deploy:/var/www/html

This image is available in your environment for any pipeline that has similar deploy steps. No more setting up secrets every time you add a repository to Drone.

Security considerations

As always, treat your secrets with care. At minimum, keep these things in mind:

  • Don't push your secrets to publicly available image repositories (e.g. public Docker Hub) or Git repositories
  • When using Drone secrets, use skip branches to avoid exposing your secrets to untrusted code