Skip to content
SumGuy's Ramblings
Go back

Woodpecker CI vs Drone CI: Lightweight Pipelines for People Who Hate Waiting

Jenkins Has a Jenkins Server to Manage Itself

The self-hosted CI landscape has a problem: the tools built for it are built like enterprise software because that’s where the money is. Jenkins is the canonical example. Jenkins is incredibly powerful. Jenkins can do anything. Jenkins also requires a dedicated server, a team of people to manage its plugins, and sometimes a second Jenkins instance to schedule jobs on the first Jenkins instance.

GitHub Actions is the modern alternative, but it’s GitHub-native. If you’re self-hosting your code on Gitea, Forgejo, or Gogs, you’re working around GitHub Actions integration, not with it. Runners exist, but you’re duct-taping a cloud-native tool to an on-premises workflow.

Drone CI was built to solve this. Container-native, simple YAML pipeline definitions, first-class Gitea integration. It worked well enough that it grew a community. Then it changed its license, that community forked it into Woodpecker CI, and now you have two very similar tools with different governance models.


Drone CI: The Original

Drone CI was created by Harness (previously just “Drone”) and takes a clean approach to CI: every pipeline step runs in a container. There’s no plugin installation, no shared state between steps except mounted volumes, and no “it works on the master node but not the agent” mysteries. The pipeline definition is a YAML file in your repo.

Drone’s key design decisions:

The License Change Drama

In 2021, Drone changed from Apache 2.0 to the Business Source License (BSL). The BSL is source-available but not open source — it restricts commercial use and “production use” above a certain scale without a paid license. For most small self-hosters the practical impact is minimal, but the philosophical shift from “open source” to “source-available” was enough for the community to fork the project.

The Woodpecker CI fork took the last Apache 2.0 version of Drone and continued development under AGPL. It’s diverged enough that there are now meaningful differences, but the pipeline YAML is still largely compatible.

Installing Drone

Drone requires an OAuth application registered in your Gitea instance:

# docker-compose.yml for Drone + Gitea
version: "3"
services:
  drone:
    image: drone/drone:2
    restart: always
    ports:
      - "3000:80"
    environment:
      - DRONE_GITEA_SERVER=https://gitea.example.com
      - DRONE_GITEA_CLIENT_ID=your-oauth-client-id
      - DRONE_GITEA_CLIENT_SECRET=your-oauth-client-secret
      - DRONE_RPC_SECRET=your-shared-rpc-secret
      - DRONE_SERVER_HOST=drone.example.com
      - DRONE_SERVER_PROTO=https
    volumes:
      - drone-data:/data

  drone-runner:
    image: drone/drone-runner-docker:1
    restart: always
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=drone.example.com
      - DRONE_RPC_SECRET=your-shared-rpc-secret
      - DRONE_RUNNER_CAPACITY=2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

volumes:
  drone-data:

A Drone Pipeline

The pipeline file goes in your repo as .drone.yml:

kind: pipeline
type: docker
name: default

steps:
  - name: test
    image: golang:1.21
    commands:
      - go test ./...

  - name: build
    image: golang:1.21
    commands:
      - go build -o app .
    when:
      branch:
        - main

  - name: docker-build-push
    image: plugins/docker
    settings:
      repo: registry.example.com/myapp
      tags:
        - latest
        - ${DRONE_COMMIT_SHA:0:8}
      registry: registry.example.com
      username:
        from_secret: registry_user
      password:
        from_secret: registry_pass
    when:
      branch:
        - main

The from_secret syntax pulls values from secrets stored in the Drone UI — never hardcode credentials in pipeline files.


Woodpecker CI: The Community Fork

Woodpecker CI started as a fork but has developed into a genuinely distinct project. It maintains a compatible pipeline syntax with Drone (most Drone pipelines work with minimal changes) but adds features Drone hasn’t prioritized and has a more active community contribution pace.

Key Woodpecker additions over Drone:

Installing Woodpecker

The setup is nearly identical to Drone:

# docker-compose.yml for Woodpecker + Gitea
version: "3"
services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:latest
    restart: always
    ports:
      - "8000:8000"
      - "9000:9000"
    environment:
      - WOODPECKER_OPEN=false
      - WOODPECKER_HOST=https://ci.example.com
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_URL=https://gitea.example.com
      - WOODPECKER_GITEA_CLIENT=your-oauth-client-id
      - WOODPECKER_GITEA_SECRET=your-oauth-client-secret
      - WOODPECKER_AGENT_SECRET=your-shared-agent-secret
    volumes:
      - woodpecker-server-data:/var/lib/woodpecker/

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:latest
    restart: always
    depends_on:
      - woodpecker-server
    environment:
      - WOODPECKER_SERVER=woodpecker-server:9000
      - WOODPECKER_AGENT_SECRET=your-shared-agent-secret
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - woodpecker-agent-config:/etc/woodpecker

volumes:
  woodpecker-server-data:
  woodpecker-agent-config:

A Woodpecker Pipeline

The pipeline file is .woodpecker.yml (or .woodpecker/ directory for multiple files):

# .woodpecker.yml
steps:
  - name: test
    image: golang:1.21
    commands:
      - go test ./...

  - name: build
    image: golang:1.21
    commands:
      - go build -o app .
    when:
      branch: main

  - name: docker
    image: woodpeckerci/plugin-docker-buildx
    settings:
      repo: registry.example.com/myapp
      tags:
        - latest
        - ${CI_COMMIT_SHA:0:8}
      registry: registry.example.com
      username:
        from_secret: registry_user
      password:
        from_secret: registry_pass
    when:
      branch: main

It’s nearly identical to Drone. The main differences: no kind: or type: fields (Woodpecker infers them), slightly different environment variable names (CI_COMMIT_SHA vs DRONE_COMMIT_SHA), and the Docker build plugin is woodpeckerci/plugin-docker-buildx instead of plugins/docker.

Multiple Pipelines

Woodpecker lets you define multiple pipeline files in a .woodpecker/ directory:

.woodpecker/
  test.yml      # Runs on every push
  build.yml     # Runs on main branch
  deploy.yml    # Runs on tags

Each file is an independent pipeline. This is cleaner than a single file with many conditional steps for different triggers.


Pipeline Comparison

Here’s the same task — test, build, push image — in both tools side by side:

Drone:

kind: pipeline
type: docker
name: ci

clone:
  disable: false

steps:
  - name: test
    image: node:20-alpine
    commands:
      - npm ci
      - npm test

  - name: build-push
    image: plugins/docker
    settings:
      repo: registry.example.com/mynode-app
      tags: ["${DRONE_COMMIT_SHA:0:8}", "latest"]
      username: {from_secret: docker_user}
      password: {from_secret: docker_pass}
    when:
      branch: [main]

Woodpecker:

steps:
  - name: test
    image: node:20-alpine
    commands:
      - npm ci
      - npm test

  - name: build-push
    image: woodpeckerci/plugin-docker-buildx
    settings:
      repo: registry.example.com/mynode-app
      tags: "${CI_COMMIT_SHA:0:8},latest"
      username: {from_secret: docker_user}
      password: {from_secret: docker_pass}
    when:
      branch: [main]

Minimal differences. If you already have Drone pipelines, migrating to Woodpecker is a find-replace-and-test operation, not a rewrite.


Woodpecker vs Drone: Side by Side

FeatureWoodpecker CIDrone CI
LicenseAGPL v3BSL (source-available)
OriginCommunity fork of DroneOriginal
Gitea integrationYesYes
GitHub integrationYesYes
Pipeline syntaxNear-identical to DroneDrone native
Multiple pipeline filesYes (.woodpecker/ dir)No (single file)
Services in stepsYesYes
Cron supportBuilt-inExternal trigger
ARM supportNativeLimited
Active developmentYes (frequent releases)Slower since BSL change
Community sizeGrowingSmaller post-fork
Commercial supportCommunity onlyHarness (paid)
Resource requirements~256MB~256MB

Gitea Integration Setup (Both Tools)

Both tools integrate with Gitea via OAuth2. In your Gitea instance:

  1. Go to Settings → Applications → OAuth2 Applications
  2. Create a new application
  3. Set the redirect URI to https://your-ci-server/login
  4. Copy the Client ID and Secret into your docker-compose environment variables

Both tools will show up in Gitea’s webhook list for each repo they’re connected to. When you push a commit, Gitea sends a webhook to the CI server, and the pipeline runs.

For Woodpecker, you can also enable automatic webhook registration — the CI server will set up the webhook in Gitea when you activate a repo in the UI, instead of requiring manual configuration.


Which One Should You Actually Use

If you’re starting fresh today: Woodpecker CI. The license is genuinely open, the development is more active, and the feature set has meaningfully extended past the Drone fork point. The pipeline syntax is compatible enough that switching later is low-friction if you change your mind.

If you’re already running Drone and it works: no urgent reason to migrate. The BSL license is unlikely to affect small self-hosters in practice. But be aware that Drone’s community momentum shifted to Woodpecker after the license change, and Woodpecker is where new features are landing.

Either way, you’re getting container-native CI that takes maybe 10 minutes to set up, runs on a cheap VPS alongside your Gitea instance, and doesn’t require a dedicated team to keep it healthy. That’s the pitch, and it holds up.


Share this post on:

Previous Post
HashiCorp Vault: Stop Hardcoding Secrets Like It's 2012
Next Post
Lazy Docker & Dive: CLI Tools That Make Docker Less Painful