From d2095d9b91b0667e8c2026ea1eb3153408501a00 Mon Sep 17 00:00:00 2001 From: Thomas Quinot Date: Sat, 18 Apr 2026 21:52:25 +0200 Subject: [PATCH] Add Drone->Woodpecker migration story --- .../2026-04-18-drone-to-woodpecker.markdown | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 _source/_posts/2026-04-18-drone-to-woodpecker.markdown diff --git a/_source/_posts/2026-04-18-drone-to-woodpecker.markdown b/_source/_posts/2026-04-18-drone-to-woodpecker.markdown new file mode 100644 index 0000000..bda2d37 --- /dev/null +++ b/_source/_posts/2026-04-18-drone-to-woodpecker.markdown @@ -0,0 +1,106 @@ +--- +layout: post +title: "Migrating from Drone CI to Woodpecker CI" +date: 2026-04-18 +tags: [ci, devops, drone, woodpecker, gitea, docker] +--- + +[Woodpecker CI](https://woodpecker-ci.org/) is a community fork of Drone CI, created when Drone changed its licensing model around 2021 and moved features behind a paid enterprise tier. Woodpecker continues as a fully open-source (Apache 2.0) project with active development, while Drone itself has been largely absorbed into Harness CI and is in maintenance mode. + +If you're running Drone with Gitea, Woodpecker is a natural migration target — it has strong Gitea integration and an actively maintained codebase. Here is what the migration actually involves, based on a real setup using Docker Compose and Traefik. + +## Docker Compose + +The server and runner/agent images change, and there are several environment variable renames. Woodpecker also uses different ports: the web UI runs on **8000** (not 80), and the agent communicates with the server over gRPC on port **9000**. + +```yaml + woodpecker: + image: woodpeckerci/woodpecker-server:latest + container_name: woodpecker + restart: unless-stopped + environment: + - WOODPECKER_HOST=https://drone.example.org # full URL including scheme + - WOODPECKER_GITEA=true + - WOODPECKER_GITEA_URL=https://gitea.example.org + - WOODPECKER_CRON_DISABLED=true + env_file: + - ./woodpecker.env + volumes: + - woodpecker:/var/lib/woodpecker # different path from Drone's /data + networks: + - services + - woodpecker + labels: + traefik.enable: "true" + traefik.http.services.woodpecker.loadbalancer.server.port: 8000 # not 80! + + woodpecker-agent: + image: woodpeckerci/woodpecker-agent:latest + restart: unless-stopped + environment: + - WOODPECKER_SERVER=woodpecker:9000 # gRPC port, not HTTP + - WOODPECKER_GRPC_SECURE=false + env_file: + - ./woodpecker.env + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - woodpecker +``` + +Make sure both the server and agent containers are on the **same Docker network**. If the agent can resolve the server's hostname but port 9000 is refused, it is likely a startup ordering issue — simply restarting the agent container after the server is fully up will fix it. + +## Environment Variables + +Your `drone.env` secrets need to be renamed in a new `woodpecker.env` file: + +| `drone.env` | `woodpecker.env` | +|---|---| +| `DRONE_GITEA_CLIENT_ID` | `WOODPECKER_GITEA_CLIENT` | +| `DRONE_GITEA_CLIENT_SECRET` | `WOODPECKER_GITEA_SECRET` | +| `DRONE_RPC_SECRET` | `WOODPECKER_AGENT_SECRET` | +| `DRONE_COOKIE_SECRET` | `WOODPECKER_SECRET` | + +Note that `WOODPECKER_AGENT_SECRET` (RPC) and `WOODPECKER_SECRET` (cookie signing) are **separate variables** — do not conflate them. + +You also need to set `WOODPECKER_ADMIN` to your Gitea username, otherwise Woodpecker will not allow you to register or access the admin panel: + +``` +WOODPECKER_ADMIN=yourusername +``` + +You will also need to create a **new OAuth application in Gitea** pointing at your Woodpecker URL, since the callback URL changes. + +## Pipeline Files + +**You must rename `.drone.yml` to `.woodpecker.yml`.** Despite what the documentation suggests, Woodpecker (at least on the `next` tag) does not fall back to `.drone.yml` — it will silently ignore it, and pushes will not trigger any pipelines. + +The pipeline syntax is largely compatible. Steps, images, commands, secrets, and `when` conditions mostly work as-is. A few things to check: + +**Move `trigger` to the pipeline level.** In Drone it was sometimes written under individual steps; in Woodpecker it must be a top-level key. + + +**Drone plugins still work.** They are just Docker images, so plugins like `drillster/drone-rsync` are fully compatible. + +**`DRONE_*` environment variables become `CI_*`.** If your pipeline scripts reference injected variables like `DRONE_COMMIT`, these are renamed in Woodpecker (e.g. `CI_COMMIT_SHA`). + +## What Does Not Migrate + +Woodpecker does not import Drone's database. This means: + +- **Build history is lost** — you start with a clean slate +- **Secrets must be re-entered** via the Woodpecker UI for each repository +- **Repos must be re-activated** in Woodpecker + +If retaining build history matters, you can keep the old Drone instance running read-only alongside Woodpecker temporarily, since they can coexist on different hostnames. + +## Summary + +For a straightforward Drone + Gitea + Docker Compose setup, the migration is low effort. The main steps are: + +1. Update `docker-compose.yml` with the new images and port numbers +2. Rename secrets in your env file and set `WOODPECKER_ADMIN` +3. Create a new OAuth app in Gitea +4. Rename `.drone.yml` to `.woodpecker.yml` in each repository +5. Move any pipeline-level `trigger` blocks to the top level +6. Re-activate repos and re-enter secrets in the Woodpecker UI