--- 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