Woodpecker CI agents: connect, label and scale your own agents
A Woodpecker agent is a process that connects to the Woodpecker server over gRPC, pulls workflows and runs them on a backend: Docker, Kubernetes or the local shell. You give it two settings, WOODPECKER_SERVER and WOODPECKER_AGENT_SECRET, then tune parallelism with WOODPECKER_MAX_WORKFLOWS and routing with labels. This guide targets Woodpecker 3.x (3.18.1 is current).
Server, agents and forges
The Woodpecker server receives webhooks from your forge, stores pipelines in its database and serves the UI on port 8000. Agents connect to the server's gRPC port (9000 by default), ask for work, run it and stream logs back. Agents never need inbound ports, so they can sit on laptops, in a home lab or in a cloud account separate from the server.
Woodpecker 3.x supports these forges in core: GitHub, Gitea, Forgejo, GitLab, Bitbucket Cloud and Bitbucket Datacenter. Woodpecker dropped Gogs in 1.0.0 (see the Gogs CI guide for alternatives); addon forges cover other systems. You configure one forge through environment variables; the docs still mark multi-forge setups as experimental.
You add your own agents when the server's bundled agent is too small, when you need arm64 or riscv64, hardware attached to the machine, or isolation between teams. On a shared instance such as ci.codeberg.org you can attach agents to your user or organization; the Codeberg CI guide shows that flow.
Prerequisites
- A running Woodpecker server with
WOODPECKER_HOSTset and an OAuth app on your forge. - Network reach from the agent to the server's gRPC address (default
:9000). Put TLS in front of it if agents connect over the internet. - Docker on agent hosts that use the docker backend, or a Kubernetes cluster for the kubernetes backend.
- A shared secret:
openssl rand -hex 32.
System token or agent token
The agent authenticates with whatever you put in WOODPECKER_AGENT_SECRET. Woodpecker accepts two kinds of value:
- System token. You set the same
WOODPECKER_AGENT_SECRETon the server and all agents. On first contact the server registers the agent, returns an ID, and the agent stores it inWOODPECKER_AGENT_CONFIG_FILE(default/etc/woodpecker/agent.conf). Persist that file or every restart creates a new agent entry. - Agent token. An admin creates the agent under Settings, Agents, Add agent, and the UI shows a token for that one agent. Organization and personal agents use the same flow under Organization settings, Agents or User settings, Agents.
Agent tokens let you revoke one machine without rotating the secret everywhere. Use WOODPECKER_AGENT_SECRET_FILE to read the value from a file or Docker secret.
Server and agent with docker compose
The upstream compose example, adapted for a Forgejo forge:
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
ports:
- 8000:8000
volumes:
- woodpecker-server-data:/var/lib/woodpecker/
environment:
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_FORGEJO=true
- WOODPECKER_FORGEJO_URL=https://forgejo.example.com
- WOODPECKER_FORGEJO_CLIENT=${WOODPECKER_FORGEJO_CLIENT}
- WOODPECKER_FORGEJO_SECRET=${WOODPECKER_FORGEJO_SECRET}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
command: agent
restart: always
depends_on:
- woodpecker-server
volumes:
- woodpecker-agent-config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=2
volumes:
woodpecker-server-data:
woodpecker-agent-config:
For GitHub use WOODPECKER_GITHUB=true with WOODPECKER_GITHUB_CLIENT and WOODPECKER_GITHUB_SECRET; Gitea, GitLab and Bitbucket follow the same pattern. Woodpecker keeps registration closed by default (WOODPECKER_OPEN=false); list admins with WOODPECKER_ADMIN.
Agent on a separate Linux VM
For an agent on another host, expose gRPC through a TLS proxy and turn on WOODPECKER_GRPC_SECURE:
docker run -d --name woodpecker-agent --restart always \
-v woodpecker-agent-config:/etc/woodpecker \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WOODPECKER_SERVER=grpc.ci.example.com:443 \
-e WOODPECKER_GRPC_SECURE=true \
-e WOODPECKER_AGENT_SECRET=<agent_token> \
-e WOODPECKER_HOSTNAME=build-arm-01 \
-e WOODPECKER_AGENT_LABELS=gpu=nvidia \
woodpeckerci/woodpecker-agent:v3 agent
Without Docker for the agent itself, install the DEB or RPM from the releases page (amd64 and arm64), copy /etc/woodpecker/woodpecker-agent.env.example to /etc/woodpecker/woodpecker-agent.env, fill in the values and start the packaged systemd unit:
sudo cp /etc/woodpecker/woodpecker-agent.env.example /etc/woodpecker/woodpecker-agent.env
sudoedit /etc/woodpecker/woodpecker-agent.env
sudo systemctl enable --now woodpecker-agent
The agent exposes a health endpoint on :3000 (WOODPECKER_HEALTHCHECK_ADDR), which you can wire into your monitoring.
Backends: docker, kubernetes, local
WOODPECKER_BACKEND picks the engine: auto-detect (default), docker, kubernetes or local.
docker
Each step runs in its own container on the agent's Docker daemon. Limit resources per step with WOODPECKER_BACKEND_DOCKER_LIMIT_MEM, WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_QUOTA or WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET, attach a network with WOODPECKER_BACKEND_DOCKER_NETWORK, and add volumes for every step with WOODPECKER_BACKEND_DOCKER_VOLUMES. Podman works through its Docker-compatible socket.
kubernetes
Each step runs as a standalone Pod, and a temporary PVC carries files between steps. Install agent and server with the official Helm chart:
helm install woodpecker oci://ghcr.io/woodpecker-ci/helm/woodpecker --version <VERSION>
Key settings: WOODPECKER_BACKEND_K8S_NAMESPACE (default woodpecker), WOODPECKER_BACKEND_K8S_NAMESPACE_PER_ORGANIZATION for one namespace per org, WOODPECKER_BACKEND_K8S_VOLUME_SIZE (default 10G), WOODPECKER_BACKEND_K8S_STORAGE_CLASS and WOODPECKER_BACKEND_K8S_STORAGE_RWX (default true; set false for RWO storage). Steps cannot set a service account, node selector or affinity unless you enable the matching *_ALLOW_FROM_STEP option on the agent.
local
Steps run as shell commands on the agent host with no isolation, in a temp directory (WOODPECKER_BACKEND_LOCAL_TEMP_DIR). The image field picks the shell, for example image: bash. It runs on Windows, macOS, FreeBSD and OpenBSD agents too, and it does not support services. A pipeline can read the agent's WOODPECKER_AGENT_SECRET, so use it only for trusted code and never as root.
Labels and agent filters
Each agent reports four default labels: platform=os/arch, hostname=..., backend=... and repo=*. Add more with WOODPECKER_AGENT_LABELS:
WOODPECKER_AGENT_LABELS=location=europe,gpu=nvidia,!secure=true
* works as a wildcard. A ! prefix makes the label mandatory: this agent only takes workflows that set secure: true. A workflow selects agents with a labels map, and every label must match:
# .woodpecker/build.yaml
labels:
platform: linux/arm64
gpu: nvidia
steps:
- name: test
image: rust
commands:
- cargo test
Agents report their own labels, so a rogue agent can claim any label. The Woodpecker docs on the main branch describe server-side agent filters, stored in the database and set in the UI when you create or edit an agent, which the agent cannot see or override. Check the release notes of the version you run before you rely on them; 3.18.1's docs do not include them yet. Organization and personal agents always receive an org-id restriction.
Parallelism, one-shot agents and the autoscaler
WOODPECKER_MAX_WORKFLOWS (default 1) sets how many workflows one agent runs at once. Raise it on large hosts, keeping in mind that all workflows share the same Docker daemon.
WOODPECKER_AGENT_SINGLE_WORKFLOW=true makes the agent exit after one workflow and forces MAX_WORKFLOWS to 1. Combine it with a VM or pod that your automation destroys on exit for one-job-per-machine isolation.
The woodpecker-ci/autoscaler project creates and removes agent VMs based on queue length. Woodpecker's docs call it not yet feature-complete. Providers: Hetzner Cloud, AWS, Linode, Vultr and Scaleway, plus DigitalOcean and OpenStack as experimental.
woodpecker-autoscaler:
image: woodpeckerci/autoscaler:next
restart: always
environment:
- WOODPECKER_SERVER=https://ci.example.com
- WOODPECKER_TOKEN=${WOODPECKER_TOKEN}
- WOODPECKER_MIN_AGENTS=0
- WOODPECKER_MAX_AGENTS=3
- WOODPECKER_WORKFLOWS_PER_AGENT=2
- WOODPECKER_GRPC_ADDR=grpc.ci.example.com
- WOODPECKER_GRPC_SECURE=true
- WOODPECKER_PROVIDER=hetznercloud
- WOODPECKER_HETZNERCLOUD_API_TOKEN=${WOODPECKER_HETZNERCLOUD_API_TOKEN}
WOODPECKER_TOKEN is a personal access token from your Woodpecker user page. The autoscaler creates an agent token per VM, so new agents must reach WOODPECKER_GRPC_ADDR from the internet. On providers that bill per started hour it keeps idle agents until a few minutes before the paid hour ends.
Security hardening
- Keep the default Require approval for setting (forked repositories) or tighten it to all pull requests on public projects.
- Only server admins can mark a repository Trusted, which allows volume mounts and privileged options. Grant it to as few repositories as you can.
- Mounting
/var/run/docker.sockinto the agent gives pipelines a path to the host. Run docker agents on dedicated VMs. - Use per-agent tokens and
*_FILEvariables instead of plain environment values. - Set
WOODPECKER_GRPC_SECURE=trueand leaveWOODPECKER_GRPC_SKIP_VERIFYoff for agents outside your network. - On Kubernetes, enable
WOODPECKER_BACKEND_K8S_NAMESPACE_PER_ORGANIZATIONfor multi-tenant clusters.
Troubleshooting
Pipeline stuck in "pending"
No connected agent matches the workflow's labels or platform. Compare the agent's labels on the Agents page with the workflow's labels block.
Agent logs "unauthorized" or keeps reconnecting
The secret differs between server and agent, or the token belongs to an agent you deleted. Generate a new agent token.
A new agent appears after every restart
The agent lost /etc/woodpecker/agent.conf. Mount it on a persistent volume.
gRPC fails through a reverse proxy
The proxy must speak HTTP/2 to the gRPC port. Point WOODPECKER_SERVER at host and port with no https:// prefix.
Kubernetes steps stay Pending
The PVC cannot bind. Set WOODPECKER_BACKEND_K8S_STORAGE_CLASS, or WOODPECKER_BACKEND_K8S_STORAGE_RWX=false if your storage lacks ReadWriteMany.
Cost trade-offs
A Woodpecker agent is light, so the cost is the machine under it. A single always-on VM is simple and cheap until builds queue up. The autoscaler cuts idle spend but adds a component you maintain, and each fresh VM starts with an empty image cache, so the first pulls on every new agent cost time. For a small team, start with one or two docker agents and size WOODPECKER_MAX_WORKFLOWS so that concurrent workflows fit in the host's CPU and RAM.
FAQ
Which Woodpecker version should the agent run?
Match the server's major version. The examples here use the v3 tag.
Can one Woodpecker agent serve several servers?
No. An agent connects to one server through WOODPECKER_SERVER. Run one agent process per server.
Does Woodpecker support Gitea and Forgejo?
Yes, both have core forge drivers. For their built-in Actions instead, see the Gitea runner guide or the Forgejo runner guide.
How do I run Woodpecker pipelines on arm64?
Start an agent on an arm64 host; it reports platform=linux/arm64. Target it with labels: platform: linux/arm64.
Managed runners with cirunner.dev
cirunner.dev provisions a fresh VM per job, registers it with your CI using a token you provide, scales with the queue and destroys the machine after the job. You choose CPU, RAM, x86_64 or arm64, region, base image and an optional GPU, and pay per compute minute.
The service is in early access with GitHub Actions and GitLab CI at launch. Woodpecker CI is on the roadmap; vote for it on the early-access form.
Skip the runner fleet
cirunner.dev boots a fresh VM for every Woodpecker CI job, registers it, and destroys it when the job ends. Woodpecker CI is on our roadmap. Vote for it on the early-access form.
Join early access