Article Image

After my last article about Setting up your own docker swarm, I wanted to take a little time and share my CI/CD stack I used for managing my applications and deploying with drone.

Where I Came From

For a long long time I used Jenkins. I don't think I've updated my jenkins host for 7 years now (yuck). I tried Blue Ocean when it first came out, and I've seen it deployed successfully, but I find myself liking pipeline definitions similar to that employed by Travis CI. I use Travis extensively for my open-source projects, but I tend to be frugal when it comes to my privately-hosted stuff.

I actually really like gitlab-ci, but I found myself not wanting to put in the effort to migrate my repos to gitlab, and I don't love that their CI platform is tightly-coupled to their repository (though I do appreciate some of the UI niceness it provides).

Introducing Drone CI

So, after a bit of searching, I landed on Drone CI. It was easy enough to set up on my home docker host, and with a little reverse-proxy magic, I was there.

Drone CI is good for simple linear pipeline-driven projects. I'm talking about things like blogs (this one), simple single-service application or microservice repositories, and the like. More complex projects like UbSub I still use Jenkins and its pipeline for. I found that it felt like I was forcing a very complex pipeline into drone ci's somewhat simpler paradigm. That's slightly on me for making ubsub a mono-repo (and 7+ services), but I'm not ready to change that yet.

I won't go over installing Drone here, they have excellent documentation for that in their docs.

Using Drone Pipeline

I bootstrapped some of my simpler and older applications to drone pretty quickly. At first, I used their docker plugin, which does DIND (or Docker-in-docker) to isolate the builds. Arguably, that produces the most idempotent way to build images since nothing is cached. That also means nothing is cached... so I opted to do my own thing.

Below is my build pipeline for this blog's Dockerfile. It requires the trusted environment in drone CI, and shares the host's instance of docker. This means that you do have to trust what it's building and that no one can insert malicious code since it's running on your host, but it does make it much faster by leveraging docker's image cache.

In addition, I store my registry credentials in drone's secret store, which are called rs0_user and rs0_pass below. If you want this to deploy as well, you'll also need to store the certificates to your remote docker machine, which I put in a volume (matching docker-machine's structure)

NOTE: Take note of any I replaced and change as necessary.

kind: pipeline
name: default

# Some common configuration to re-use below
  registry: &registry
      from_secret: rs0_user
      from_secret: rs0_pass

  # Step 1. Build the image (using host cache)
  - name: docker
    image: docker:17
    - name: docker-socket
      path: /var/run/docker.sock
      - docker build -t ${DRONE_REPO_NAME} .

  # Step 2. Login to the registry, tag the image as appropriate (after the job), and push
  #   Only if master
  - name: push
    image: docker:17
      <<: *registry
      - name: docker-socket
        path: /var/run/docker.sock
      - docker tag ${DRONE_REPO_NAME} $DOCKER_REGISTRY/${DRONE_REPO_NAME}:latest
      - docker push $DOCKER_REGISTRY/${DRONE_REPO_NAME}
        - master

  # Step 3. Deploy `stack.yaml` to the docker swarm (Leave out if wanted)
  #   Only runs if master
  - name: deploy
    image: docker:17
    environment: # Now point to the remote docker rather than the host docker
      <<: *registry
      DOCKER_HOST: "tcp://"
      DOCKER_CERT_PATH: /opt/docker-certs
      - name: s0-certs
        path: /opt/docker-certs
      - docker stack deploy --with-registry-auth --compose-file stack.yaml ${DRONE_REPO_NAME}
        - master

# Stores the certs for docker in a volume to use to deploy
  - name: s0-certs
      path: /opt/docker-certs/
  - name: docker-socket
      path: /var/run/docker.sock

And for good measure, here's the stack.yaml file I use to define the service. You can read more in my previous post about my swarm setup.

version: '3.3'
      - traefik-net
      replicas: 2
        - "traefik.enable=true"
        - "traefik.port=80"
        - ""
        limits: { cpus: '0.1', memory: '16M' }
        reservations: { cpus: '0.025', memory: '8M' }

      name: 'common_traefik-net'


You can read more on the docker series here

Blog Logo

Christopher LaPointe


Interested in Related Posts from this Site?

Virtual PDF Network Printer

May 04, 2023: For my [paperless-ngx]( install, I wanted to be able to easily "print" to it from...

Set up your own mailserver with Maddy

February 10, 2022: About a year ago I wanted to expand beyond namecheap's simple "email forwarding" service and...

Extracting a Certificate from Traefik/acme.json

February 04, 2022: It's a fairly common need to extract a certificate and key from traefik to use...

Docker Samba / CIFS Volume (Photoview)

July 12, 2021: Recently I had a need to mount a CIFS volume as a docker volume: The...

Self-Hosted Analytics: Docker & Privacy-First

July 02, 2021: I have a lot of little sites. Probably too many. Includes various personal sites, sites...

Docker Swarm Registry and Auto Garbage Collection on NFS

March 15, 2021: After moving Traefik to v2, I also updated the common registry infrastructure. Namely, this stack...


Chris LaPointe

Another site of Code

Back to Overview