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:
- Container-first: every step is a Docker image + command, nothing else
- No shared state: steps communicate through mounted workspaces
- Simple webhook integration: Gitea, GitHub, GitLab, Bitbucket via OAuth
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:
- Multiple workflows per repo: separate YAML files for different pipelines (test, build, deploy)
- Pipeline steps as services: run a database container alongside your test step without a separate service definition
- Cron job support: schedule pipelines without a webhook trigger
- Improved secrets management: secrets can be scoped to specific pipelines or steps
- Better multi-architecture support: native ARM pipeline support
- Active development: more frequent releases and bug fixes than Drone post-BSL
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
| Feature | Woodpecker CI | Drone CI |
|---|---|---|
| License | AGPL v3 | BSL (source-available) |
| Origin | Community fork of Drone | Original |
| Gitea integration | Yes | Yes |
| GitHub integration | Yes | Yes |
| Pipeline syntax | Near-identical to Drone | Drone native |
| Multiple pipeline files | Yes (.woodpecker/ dir) | No (single file) |
| Services in steps | Yes | Yes |
| Cron support | Built-in | External trigger |
| ARM support | Native | Limited |
| Active development | Yes (frequent releases) | Slower since BSL change |
| Community size | Growing | Smaller post-fork |
| Commercial support | Community only | Harness (paid) |
| Resource requirements | ~256MB | ~256MB |
Gitea Integration Setup (Both Tools)
Both tools integrate with Gitea via OAuth2. In your Gitea instance:
- Go to Settings → Applications → OAuth2 Applications
- Create a new application
- Set the redirect URI to
https://your-ci-server/login - 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.