Build & Run: Drone (Open Source Edition)

For quite some time I wanted to give Drone a spin. As I had a Gitea running on my Kubernetes cluster already it looked to me like the perfect match. Unfortunately getting started, with the constraints of running the Open Source version on an ARM-based architecture (Raspberry Pis), wasn't as easy as I first thought…

What is the difference between Open Source and Enterprise?

The Open Source Edition is fully capable of executing complex pipelines but is optimized to meet basic needs of individuals and small teams.

The Enterprise Edition:

  • Supports distributed runners
  • Supports kubernetes runners
  • Supports organization secrets, vault secrets, etc
  • Supports cron scheduling
  • Supports scalable storage (postgres, mysql, s3)
  • Supports autoscaling
  • Supports extensions

The Open Source Edition:

  • Lacks support for extensions
  • Lacks support for distributed runners
  • Lacks support for kubernetes runners
  • Lacks support for organization secrets, vault secrets, etc
  • Lacks support for cron scheduling
  • Lacks support for autoscaling
  • Limited to a single machine
  • Limited to an embedded sqlite database

Read more about at: https://docs.drone.io/enterprise/

Building Drone

Github has a good explanation on how to build the Open Source Edition of Drone. Unfortunately they are not releasing the free version as ready-to-use image on Docker Hub. At least there's a Dockerfile to build the standard drone-server on ARM. So I came up with my own version of a Dockerfile:

# docker build --rm -f docker/Dockerfile -t drone/drone .

# Build stage
###################################

# https://hub.docker.com/_/golang?tab=tags
FROM golang:1.13-alpine3.11 AS build-env

ARG DRONE_VERSION
ARG GOARCH
ARG GOOS
ENV GOARCH=${GOARCH:-arm}
ENV GOOS=${GOOS:-linux}

# Build deps
RUN apk --no-cache add build-base ca-certificates git

# Clone repo
RUN git clone https://github.com/drone/drone.git /go/src/github.com/drone/drone

# Change Workdir
WORKDIR /go/src/github.com/drone/drone

# Checkout version tag if set
RUN if [ -n "${DRONE_VERSION}" ]; then git checkout -b "${DRONE_VERSION}" "tags/${DRONE_VERSION}"; fi \
    && echo "Build Drone (Open Source Edition) for ${GOOS}/${GOARCH} ..." \
    && GOARCH="${GOARCH}" GOOS="${GOOS}" go install -tags "oss nolimit" github.com/drone/drone/cmd/drone-server

# Final stage
###################################

# https://hub.docker.com/_/alpine?tab=tags
FROM alpine:3.11

EXPOSE 80 443

VOLUME /data

# Borrowed from: https://github.com/drone/drone/blob/master/docker/Dockerfile.server.linux.arm
RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf

ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=arm
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
ENV DRONE_DATADOG_ENABLED=false

COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build-env /go/bin/drone-server /bin/drone-server

CMD ["/bin/drone-server"]

Note: A build takes approximately 11 minutes on a Raspberry Pi 4.

Running Drone

The setup guide for Gitea is pretty straight forward, but it is written for the Enterprise edition and expects you to install a separate Runner, which isn't supported by the OSS version. After some googling, I found an option called DRONE_AGENTS_DISABLED that isn't mentioned anywhere in the Server reference.

But the Changelog for version 1.5.0 states:

The server has been changed to run in multi-machine mode by default. If you are already running the server in multi-machine mode (e.g. with agents) this does not impact you. If you are running the server in single-machine mode you must set the following configuration parameter moving forward:

DRONE_AGENTS_DISABLED=true

Please note that failing to set this parameter will result in your builds sitting in the queue, in a pending state, waiting for agents to connect to the server and process your build.

Putting things together I came up with the following command:

docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  --volume=/var/lib/drone:/data \
  --env=DRONE_GITEA_SERVER={{DRONE_GITEA_SERVER}} \
  --env=DRONE_GITEA_CLIENT_ID={{DRONE_GITEA_CLIENT_ID}} \
  --env=DRONE_GITEA_CLIENT_SECRET={{DRONE_GITEA_CLIENT_SECRET}} \
  --env=DRONE_SERVER_PROTO={{DRONE_SERVER_PROTO}} \
  --env=DRONE_SERVER_HOST={{DRONE_SERVER_HOST}} \
  --env=DRONE_AGENTS_DISABLED=true \
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone

Note: You don't have to specify DRONE_RPC_SECRET since you are running in single-machine mode. And you need to provide a volume for the docker.sock to server instead of the (not existing) agent.

Everytime I tried to initiate an automated build it got stuck in "Pending…" state.

I found two valuable options DRONE_LOGS_DEBUG and DRONE_LOGS_TRACE in the server reference to dig into this problem.

This time running the command with trace logs enabled drone gave me the following output:

runner:
  local: false
  image: drone/controller:1
  platform: linux/amd64
  os: linux
  arch: arm
  kernel: ""
  variant: ""
  machine: {{machine_id}}
  capacity: 2
  labels: {}
  volumes: []
  networks: []
  devices: []
  privileged: []
  environ: {}
  limits:
    memswaplimit: 0
    memlimit: 0
    shmsize: 0
    cpuquota: 0
    cpushares: 0
    cpuset: ""

After fiddling around, I am now running Drone with the following options:

docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  --volume=/var/lib/drone:/data \
  --env=DRONE_GITEA_SERVER={{DRONE_GITEA_SERVER}} \
  --env=DRONE_GITEA_CLIENT_ID={{DRONE_GITEA_CLIENT_ID}} \
  --env=DRONE_GITEA_CLIENT_SECRET={{DRONE_GITEA_CLIENT_SECRET}} \
  --env=DRONE_SERVER_PROTO={{DRONE_SERVER_PROTO}} \
  --env=DRONE_SERVER_HOST={{DRONE_SERVER_HOST}} \
  --env=DRONE_AGENTS_DISABLED=true \
  --env=DRONE_RUNNER_LOCAL=true \
  --env=DRONE_RUNNER_PLATFORM=linux/arm \
  --env=DRONE_RUNNER_ARCH=arm \
  --env=DRONE_RUNNER_IMAGE=drone/drone-runner-docker:latest \
  --env=DRONE_RUNNER_CAPACITY=1 \
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone

Note: DRONE_RUNNER_IMAGE has been changed from drone/controller to drone/drone-runner-docker.

Boy, what a ride…

Bonus: Building Drone with Drone

Rounding things up, I wanted to build Drone with Drone. Here's my .drone.yml file:

---
kind: pipeline
type: docker
name: linux-arm

# Required form ARM: defaults to Linux amd64
platform:
  arch: arm
  os: linux

steps:
  - name: Build & Publish
    image: plugins/docker:18
    settings:
      dockerfile: docker/Dockerfile
      repo: {{registry}}/drone/drone
      build_args:
        - DRONE_VERSION=v1.7.0
      tags:
        - "v1.7.0"
        - "latest"

trigger:
  event:
    - push
    - tag

links

social