How to Install Woodpecker CI on Ubuntu 24.04 VPS: Lightweight Open-Source Drone CI Fork
Continuous integration is the connective tissue of modern development, and most teams end up paying a surprising amount of money for it. SaaS CI platforms bill per build minute, per seat, and often per concurrent job, which punishes you exactly when velocity goes up. Self-hosting a CI server on your own VPS flips that cost curve upside down: you pay a flat monthly rate and run as many builds as your hardware can handle. This guide walks you through installing Woodpecker CI, a community-driven fork of Drone, on an Ubuntu 24.04 VPS. By the end, you will have a Docker Compose stack with a Woodpecker server and agent, OAuth login wired up to Gitea, an HTTPS reverse proxy with a valid certificate, and your first pipeline running on every push.
Need a server first? Deploy Ubuntu 24.04 on our CloudCore Professional plan and have Woodpecker CI online in well under an hour.
Table of Contents
What is Woodpecker CI?
Woodpecker CI is a simple, lightweight, open-source continuous integration engine driven by a YAML pipeline file in your repository. It was forked from Drone 0.8 in 2019 after Drone's maintainers moved the project onto a business-source license, and has since grown into a fully independent CI system with active community development under the Apache 2.0 license.
Architecturally, Woodpecker has two moving parts. The server is a single Go binary that talks to your Git forge over OAuth, receives webhooks, stores pipeline state in SQLite or Postgres, and serves the web UI. Agents are separate processes that connect outbound to the server, pull pending jobs, and execute pipeline steps. Each step runs inside a container, so your build environment is reproducible and throwaway. Because the agent connects out to the server (not the other way around), you can place agents anywhere on the internet -- in a different region, behind NAT, on a beefier build box -- and they will register themselves automatically.
The pipeline definition lives in a file called .woodpecker.yml (or a directory .woodpecker/ holding multiple workflow files) at the root of each repository. The syntax will look instantly familiar to anyone coming from Drone, GitLab CI, or GitHub Actions: a list of named steps, each with an image, commands, and optional triggers. Woodpecker resolves the DAG automatically and runs steps in parallel where possible.
Out of the box Woodpecker supports Gitea, Forgejo, GitHub, GitLab, and Bitbucket as Git forges, with OAuth2 for authentication. It supports Docker, local, and Kubernetes execution backends, matrix builds for testing multiple language or OS versions in parallel, secrets with per-repository or organization-wide scoping, cron schedules for nightly builds, and a mature plugin ecosystem covering Docker registry pushes, SSH deployments, Slack notifications, S3 uploads, and dozens more.
Why Self-Host CI Instead of Using Drone Cloud or GitHub Actions?
Hosted CI platforms are fine for hobby projects, but once a team starts shipping daily they bring real friction.
- Predictable, flat cost -- A VPS costs the same whether you run 10 builds a week or 1,000. GitHub Actions charges per minute beyond the free tier; CircleCI meters credits; Drone Cloud charges per seat. Self-hosted Woodpecker lets a five-person team run continuous builds around the clock for the price of a single VPS.
- Full control over the build environment -- You pick the kernel, the Docker version, the amount of disk available for build caches, and the network routes to your private registries. Hosted runners give you a sandbox; a self-hosted agent is your machine.
- Secrets never leave your perimeter -- Production deployment keys, signing certificates, and customer credentials stay on hardware you control. No third-party breach can leak them.
- Fast access to private resources -- Agents that need to deploy to a private Kubernetes cluster, push to a local Docker registry, or run integration tests against a VPC database work without VPN acrobatics when they share a network with those resources.
- No minute caps or queue waits -- Public runners get congested. Your own agent is idle the instant your last build finishes.
- Freedom from vendor lock-in -- Woodpecker is Apache-licensed and community-governed. No single company can change the pricing model, sunset a feature, or relicense it out from under you the way Drone's upstream did.
CI Cost Comparison at Typical Team Scale
| Scenario | GitHub Actions | Drone Cloud | CircleCI | Self-Hosted Woodpecker |
|---|---|---|---|---|
| Monthly cost (5 devs, ~3k min/mo) | ~$30-50/mo | Paid tier + per-seat | ~$30-100/mo | EUR 19.99/mo (flat) |
| Concurrent jobs | 20 (Team) | 2-5 | 4-30 | Hardware-limited |
| Build minute cap | Yes | Yes | Yes | None |
| Private network access | Self-hosted runner required | Self-hosted required | Self-hosted required | Native |
| Custom OS/kernel | Limited | Limited | Docker only | Full |
| Secrets exfiltration risk | Third-party | Third-party | Third-party | In-house |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access.
- SSH access to your server.
- A domain pointed at your server's public IP (for example
ci.example.com). OAuth callbacks require a resolvable hostname, so plain IPs will not work cleanly. - At least 4 GB of RAM (8-12 GB is strongly recommended once you start running real pipelines in parallel).
- An account on a supported Git forge -- Gitea, Forgejo, GitHub, GitLab, or Bitbucket -- where you can create an OAuth application.
Recommended Plan: CloudCore Professional>
Woodpecker itself is tiny, but build pipelines are hungry. Node test suites, Rust compilation, Docker-in-Docker, and Playwright all consume CPU and RAM aggressively. We recommend the CloudCore Professional plan:>
- 6 vCPU cores
- 12 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth
- EUR 19.99/month>
This gives you plenty of headroom for the server, at least two concurrent agents, and layer caching on disk. If you want Woodpecker to live next to a Git forge on the same box, pair this with our Gitea install guide first.
Connect to your server via SSH before continuing:
ssh root@your-server-ipStep 1: Update System Packages
Keeping the base image current matters even more on a CI host, because your agents will be pulling images, writing caches, and touching the kernel's container subsystems constantly. Start with a clean slate.
sudo apt update && sudo apt upgrade -yExpected output (abbreviated):
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.If the kernel was updated, reboot now:
sudo rebootStep 2: Install Docker and Docker Compose
Woodpecker runs as two containers, and each pipeline step runs in its own container, so Docker Engine plus the Compose plugin is mandatory. If you do not already have Docker installed, follow our dedicated Docker install guide for a full walkthrough. The short version is below.
Remove any old Docker-related packages shipped by Ubuntu:
sudo apt remove -y docker docker-engine docker.io containerd runc || trueInstall the prerequisites and add Docker's official repository:
sudo apt install -y ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine plus the Compose plugin:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginVerify both components:
docker --version
docker compose versionExpected output:
Docker version 27.1.2, build 241b696
Docker Compose version v2.29.2Enable the service so it comes back after reboots:
sudo systemctl enable --now dockerStep 3: Create an OAuth Application on Your Git Backend
Woodpecker does not manage its own user database. Authentication is delegated entirely to your Git forge, and the admin list is keyed by forge usernames. You therefore need to register an OAuth2 application on whichever forge you plan to integrate.
This guide uses Gitea as the primary example because it is the most common self-hosted pairing. The same callback pattern applies to GitHub and GitLab.
Gitea
Woodpecker CI
- Redirect URI: https://ci.example.com/authorize
Need push-to-main pipelines from Gitea's own Actions as well? See our companion guide: How to Install Gitea Actions on Ubuntu.
GitHub
In GitHub, go to Settings -> Developer settings -> OAuth Apps -> New OAuth App and set the callback URL to https://ci.example.com/authorize. The rest of the flow is identical.
GitLab
In GitLab, open User Settings -> Applications, create a new application with api and read_user scopes, and use https://ci.example.com/authorize as the redirect URI.
Keep the Client ID and Secret handy. The next step consumes them.
Step 4: Write the Docker Compose Stack
Create a working directory for Woodpecker and its persistent data:
sudo mkdir -p /opt/woodpecker
sudo chown "$USER":"$USER" /opt/woodpecker
cd /opt/woodpeckerGenerate a strong shared secret. This value authenticates agents to the server, so treat it like a password:
openssl rand -hex 32Copy the output -- you will paste it into both services below.
Create a .env file for secrets so they stay out of the Compose file:
cat > .env <<'EOF'
--- Git forge OAuth (Gitea example) ---
WOODPECKER_GITEA=true
WOODPECKER_GITEA_URL=https://git.example.com
WOODPECKER_GITEA_CLIENT=paste-your-gitea-client-id
WOODPECKER_GITEA_SECRET=paste-your-gitea-client-secret--- Shared cluster secret (openssl rand -hex 32) ---
WOODPECKER_AGENT_SECRET=paste-the-generated-hex-string--- Admin users (comma-separated forge usernames) ---
WOODPECKER_ADMIN=your-gitea-username--- Public URL of the server ---
WOODPECKER_HOST=https://ci.example.com
EOF
chmod 600 .envIf you are using GitHub instead, replace the Gitea block with:
WOODPECKER_GITHUB=true
WOODPECKER_GITHUB_CLIENT=...
WOODPECKER_GITHUB_SECRET=...And for GitLab:
WOODPECKER_GITLAB=true
WOODPECKER_GITLAB_URL=https://gitlab.com
WOODPECKER_GITLAB_CLIENT=...
WOODPECKER_GITLAB_SECRET=...Now create the Compose file itself:
cat > docker-compose.yml <<'EOF' services: woodpecker-server: image: woodpeckerci/woodpecker-server:latest container_name: woodpecker-server restart: unless-stopped ports: - "127.0.0.1:8000:8000" # HTTP (Nginx will front this) - "9000:9000" # gRPC for remote agents volumes: - woodpecker-server-data:/var/lib/woodpecker/ environment: - WOODPECKER_OPEN=true - WOODPECKER_HOST=${WOODPECKER_HOST} - WOODPECKER_ADMIN=${WOODPECKER_ADMIN} - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} # Git forge (pass through whichever block you set in .env) - WOODPECKER_GITEA=${WOODPECKER_GITEA:-} - WOODPECKER_GITEA_URL=${WOODPECKER_GITEA_URL:-} - WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT:-} - WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET:-} - WOODPECKER_GITHUB=${WOODPECKER_GITHUB:-} - WOODPECKER_GITHUB_CLIENT=${WOODPECKER_GITHUB_CLIENT:-} - WOODPECKER_GITHUB_SECRET=${WOODPECKER_GITHUB_SECRET:-} - WOODPECKER_GITLAB=${WOODPECKER_GITLAB:-} - WOODPECKER_GITLAB_URL=${WOODPECKER_GITLAB_URL:-} - WOODPECKER_GITLAB_CLIENT=${WOODPECKER_GITLAB_CLIENT:-} - WOODPECKER_GITLAB_SECRET=${WOODPECKER_GITLAB_SECRET:-}woodpecker-agent: image: woodpeckerci/woodpecker-agent:latest container_name: woodpecker-agent restart: unless-stopped depends_on: - woodpecker-server volumes: - /var/run/docker.sock:/var/run/docker.sock - woodpecker-agent-config:/etc/woodpecker environment: - WOODPECKER_SERVER=woodpecker-server:9000 - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} - WOODPECKER_MAX_WORKFLOWS=4
volumes: woodpecker-server-data: woodpecker-agent-config: EOF
A few notes on the settings you see:
WOODPECKER_OPEN=trueallows any user on your Git forge to sign in. Flip this tofalseon a private instance and add users explicitly via the admin UI.WOODPECKER_ADMINis a comma-separated list of forge usernames who get admin rights automatically on first login. Add yourself here.- Port 8000 is bound to
127.0.0.1because Nginx will terminate TLS and proxy to it. Do not expose it publicly. - Port 9000 is the gRPC endpoint that agents connect to. If all your agents live on the same host, you can comment it out; remote agents need it.
WOODPECKER_MAX_WORKFLOWS=4controls how many pipelines this agent runs concurrently. Tune to match your CPU count.- The
docker.sockmount is how the agent spawns step containers. This grants root-equivalent access to the host, which is fine on a dedicated CI VPS but be aware of it.
woodpecker-server-data volume, which is perfect for teams up to a few dozen users. For heavy usage switch to Postgres by adding WOODPECKER_DATABASE_DRIVER=postgres and WOODPECKER_DATABASE_DATASOURCE=... and adding a Postgres service.Step 5: Boot Woodpecker and Become the First Admin
With the Compose file and .env in place, start the stack:
cd /opt/woodpecker
docker compose up -dExpected output:
[+] Running 3/3
! Network woodpecker_default Created
! Volume "woodpecker_woodpecker-server-data" Created
! Volume "woodpecker_woodpecker-agent-config" Created
! Container woodpecker-server Started
! Container woodpecker-agent StartedVerify both containers are healthy:
docker compose psExpected output:
NAME IMAGE STATUS PORTS
woodpecker-agent woodpeckerci/woodpecker-agent:latest Up 10 seconds
woodpecker-server woodpeckerci/woodpecker-server:latest Up 10 seconds 127.0.0.1:8000->8000/tcp, 0.0.0.0:9000->9000/tcpTail the logs to catch any misconfiguration early:
docker compose logs -f woodpecker-serverYou should see lines like level=info msg="starting Woodpecker server..." and level=info msg="starting http server..." with no error stacks. Press Ctrl+C to stop following.
We will finish the TLS setup in Step 7. For the moment, you can reach the UI by tunneling port 8000:
ssh -L 8000:127.0.0.1:8000 root@your-server-ipOpen http://localhost:8000 in your browser, click Login, and complete the OAuth dance. Because your username is in WOODPECKER_ADMIN, the top-right menu will show a Repositories list and an Admin settings area.
Click Add Repository, pick a repo from your forge, and enable it. Woodpecker registers a webhook on that repo automatically.
Step 6: Commit Your First .woodpecker.yml Pipeline
Pipelines are code. Woodpecker looks for one or more YAML files in .woodpecker/ (or a single .woodpecker.yml) at the repo root. Create a minimal file in a repository you enabled above:
# .woodpecker.yml steps: - name: lint image: node:20-alpine commands: - npm ci - npm run lint- name: test image: node:20-alpine commands: - npm ci - npm test depends_on: [lint]
- name: build image: node:20-alpine commands: - npm ci - npm run build depends_on: [test] when: branch: main
Commit and push. Within a second or two the server receives the webhook, the agent picks up the job, and each step runs inside its own ephemeral container. Watch it live in the UI: you will see real-time log streaming, a DAG visualization, and the final status badge.
A few pipeline patterns worth knowing from day one:
when:clauses control when a step runs. Common filters arebranch,event(push,pull_request,tag,cron),path, andstatus.depends_on:wires the DAG manually. Omit it and steps run in declaration order with implicit dependencies.services:starts sidecar containers (databases, caches) visible to all steps in the same pipeline. Perfect for integration tests.failure: ignorelets a step fail without marking the whole pipeline red -- useful for non-critical linters.
Step 7: Expose Woodpecker via Nginx with TLS
OAuth callbacks over plain HTTP are a non-starter. Nginx plus Certbot gives you a clean TLS termination point.
Install Nginx and the Certbot Nginx plugin:
sudo apt install -y nginx certbot python3-certbot-nginxCreate a reverse proxy configuration:
sudo tee /etc/nginx/sites-available/woodpecker > /dev/null <<'EOF' server { listen 80; server_name ci.example.com;# Certbot will replace this block with an HTTPS redirect. location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# Live log streaming requires long-lived connections proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; proxy_read_timeout 600s; proxy_send_timeout 600s; }
client_max_body_size 50m; } EOF
sudo ln -s /etc/nginx/sites-available/woodpecker /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx
Now obtain a Let's Encrypt certificate:
sudo certbot --nginx -d ci.example.comCertbot rewrites the config to listen on 443 with a valid cert, adds an HTTP-to-HTTPS redirect, and installs a systemd timer that renews certificates every 12 hours. Visit https://ci.example.com and confirm the padlock shows a green certificate.
Finally, lock down the firewall so only the ports you actually need are reachable:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw allow 9000/tcp # Only if you plan to run remote agents
sudo ufw enableIf all your agents run on the same host, omit 9000/tcp; the agent talks to the server over the internal Docker network.
Step 8: Register Additional Agents
One agent is enough to prove the stack works, but in production you will want several so builds run in parallel without queuing. Woodpecker supports two registration modes: shared secret (what we configured in Step 4) and agent registration tokens.
Shared Secret (Simplest)
Any machine that knows WOODPECKER_AGENT_SECRET and can reach port 9000 on the server can register itself. On a second VPS with Docker installed, run:
docker run -d --name woodpecker-agent \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WOODPECKER_SERVER=ci.example.com:9000 \
-e WOODPECKER_AGENT_SECRET=paste-the-same-hex-string \
-e WOODPECKER_GRPC_SECURE=true \
-e WOODPECKER_MAX_WORKFLOWS=4 \
woodpeckerci/woodpecker-agent:latestWOODPECKER_GRPC_SECURE=true wraps the gRPC connection in TLS using the certificate on your Nginx (only works once you have set up a certificate on port 9000 too -- easiest way is a second server block in Nginx that proxies grpcs:// to grpc://127.0.0.1:9000).
Agent Registration Tokens
For more auditable setups, generate per-agent tokens in the UI. Open the admin area -> Agents -> Add Agent. The server prints a one-time registration token. Start the agent with:
-e WOODPECKER_AGENT_TOKEN=paste-registration-tokenThe agent exchanges the token for a long-lived credential on first connect, and you can revoke individual agents from the UI without rotating the cluster-wide secret.
Tokens are the right choice once you have more than a handful of agents or agents running in environments with mixed trust levels.
Step 9: Use Secrets, Matrix Builds, and Plugins
With the basics in place, three features make Woodpecker genuinely competitive with the big CI SaaS platforms.
Secrets
Secrets are injected as environment variables into steps that request them. Add a secret in the UI (per repository or org-wide) named DOCKER_PASSWORD, then reference it in .woodpecker.yml:
steps:
- name: publish
image: plugins/docker
settings:
repo: example/app
tags: latest
username: your-docker-username
password:
from_secret: DOCKER_PASSWORD
when:
event: tagBy default secrets are only exposed to trusted images (configured per-secret). This prevents a malicious PR from swapping the image and exfiltrating the value.
Matrix Builds
Matrix is a first-class feature. To test a Go library against three Go versions and two OSes in parallel:
matrix: GO_VERSION: - "1.21" - "1.22" - "1.23" OS: - linux - alpine
steps: - name: test image: golang:${GO_VERSION}-${OS} commands: - go test ./...
Woodpecker fans this out into six independent pipelines. Each runs concurrently up to the agent's WOODPECKER_MAX_WORKFLOWS cap, then queues the rest.
Plugins
Plugins are regular Docker images that follow a light convention: they read their configuration from environment variables prefixed with PLUGIN_. The official plugin directory lists community- and core-maintained plugins for Docker, Slack, S3, SSH, Git, Gitea releases, Kubernetes, and much more. Drone plugins almost always work unmodified because the prefix is identical.
A typical deploy step looks like:
- name: deploy
image: appleboy/drone-ssh
settings:
host: prod.example.com
username: deploy
key:
from_secret: SSH_KEY
script:
- cd /opt/app && ./deploy.sh
when:
event: tagRunning Woodpecker on Kubernetes
If you already run a Kubernetes cluster, the Kubernetes backend lets the agent schedule each pipeline step as a native pod instead of a Docker container. The server can still run anywhere -- on a VPS, in the cluster, or elsewhere -- while the agent lives inside Kubernetes and drives the scheduler.
The two settings that flip the switch are:
woodpecker-agent:
environment:
- WOODPECKER_BACKEND=kubernetes
- WOODPECKER_BACKEND_K8S_NAMESPACE=woodpecker-builds
- WOODPECKER_BACKEND_K8S_STORAGE_CLASS=fast
- WOODPECKER_SERVER=ci.example.com:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}The agent needs a ServiceAccount with permission to create pods, PVCs, and secrets in that namespace. The official Kubernetes deployment guide ships a ready-made Helm chart. Once running, every step in every pipeline becomes a real pod -- which means node autoscaling, GPU scheduling, and Kubernetes-native RBAC all apply to your CI jobs without extra work.
For small teams the Docker backend we configured in this guide is perfectly sufficient. The Kubernetes backend pays off once you are running hundreds of concurrent pipelines or want to share cluster capacity between CI and application workloads.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
OAuth returns redirect_uri_mismatch | Callback URL in the forge app does not match WOODPECKER_HOST | Update the OAuth app's redirect URI to ${WOODPECKER_HOST}/authorize exactly, including scheme and trailing path |
Agent logs rpc error: code = Unauthenticated | Shared secret mismatch | Verify both containers read the same WOODPECKER_AGENT_SECRET from .env; redeploy with docker compose up -d |
Pipeline stuck in pending forever | No agent connected, or no agent with matching labels | Check docker compose logs woodpecker-agent. If you use labels, ensure a step's labels: block matches an online agent |
Steps fail with docker: Cannot connect to the Docker daemon | Agent missing /var/run/docker.sock mount | Confirm the volumes: section on the agent mounts the host socket |
| UI shows blank log panel during builds | Nginx not configured for websocket/upgrade headers | Ensure the proxy block contains proxy_set_header Upgrade and proxy_buffering off |
Certbot fails with connection refused | Firewall blocking port 80 during HTTP-01 challenge | sudo ufw allow 'Nginx Full' before rerunning Certbot |
docker pull rate-limited inside pipelines | Anonymous Docker Hub limits | Add Docker Hub credentials as a secret and log in during the pipeline, or mirror images into a private registry |
| Out-of-memory kills on big builds | Default VPS swap is small | Add 4 GB of swap: sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile |
Viewing Logs
cd /opt/woodpecker
docker compose logs -f woodpecker-server
docker compose logs -f woodpecker-agentPress Ctrl+C to stop following. Per-build logs also live inside each step's container and are streamed to the UI in real time.
FAQ
Is Woodpecker CI a drop-in replacement for Drone CI?
Yes, for most intents and purposes. Woodpecker forked Drone 0.8 before Harness.io relicensed Drone under a business-source license. Pipeline syntax is nearly identical, most Drone plugins work unchanged, and migration is usually a matter of renaming .drone.yml to .woodpecker.yml, pointing the OAuth callback at the new server, and reconfiguring secrets. The one area where the projects diverge is governance: Woodpecker is community-maintained under Apache 2.0, while Drone's licensing is now tied to Harness's commercial strategy.
How much RAM does Woodpecker CI need?
The server itself runs comfortably in under 256 MB. The agent's resource footprint is dominated by whatever your pipeline steps do inside their containers -- a Go compile is light, a Playwright suite is heavy. For small teams running typical Go, Node, or Python builds, a 4 GB VPS handles server plus one agent. For parallel pipelines, Docker-in-Docker steps, or multi-agent setups, plan for 8-12 GB such as our CloudCore Professional plan.
Does Woodpecker support matrix builds?
Yes. Woodpecker supports matrix pipelines out of the box. Declaring a matrix: section in .woodpecker.yml fans out one pipeline per combination, which is ideal for testing multiple language versions or target platforms in parallel. Each combination runs independently and the overall pipeline status reflects the combined result.
Can Woodpecker run pipelines on Kubernetes?
Yes. Woodpecker ships a Kubernetes backend that schedules each pipeline step as a native pod. Set WOODPECKER_BACKEND=kubernetes on the agent and it picks up jobs from the server while running the actual workload inside your cluster. This is the recommended approach once you are running hundreds of concurrent pipelines, need GPU scheduling, or want to share autoscaled nodes between CI and application workloads.
How do I register additional agents?
Generate an agent registration token in the server admin panel, then start a new agent container on any Docker host with WOODPECKER_SERVER and WOODPECKER_AGENT_TOKEN set. The agent registers itself on first connect and exchanges the one-time token for a long-lived credential. Shared-secret mode also works and is simpler for a homogeneous cluster, but per-agent tokens give you auditable revocation.
How does Woodpecker compare to Gitea Actions, Jenkins, and GitHub Actions Self-Hosted Runners?
Gitea Actions is a GitHub-Actions-compatible runner built into Gitea. It is perfect if you already run Gitea and want zero extra infrastructure -- but it is tightly coupled to Gitea and less featureful than Woodpecker for cross-forge setups. See our Gitea Actions install guide for that path.
Jenkins is the old guard: extremely flexible via its plugin ecosystem, but XML-heavy, Groovy-heavy, and resource-heavy. Woodpecker's YAML-first, container-native model is a better fit for modern stacks.
GitHub Actions self-hosted runners work well if you live entirely on GitHub, but they require installing a binary per runner, run jobs outside Docker by default, and are tied to GitHub's workflow syntax. Woodpecker gives you the same containerized isolation with a forge-agnostic server.
For a lean, modern, container-first CI on your own infrastructure that supports Gitea, GitHub, GitLab, and Bitbucket with a single server, Woodpecker is hard to beat.
Next Steps
Now that Woodpecker is running on your VPS, here are high-value follow-ups:
- Pair it with a self-hosted Gitea -- Run Gitea and Woodpecker side by side for an entirely in-house code-and-CI stack. Start with our Gitea install guide.
- Add Gitea Actions for lightweight jobs -- Use Gitea's built-in runner for small per-repo tasks and Woodpecker for heavier cross-repo pipelines. Install Gitea Actions on Ubuntu.
- Scale out agents on a second VPS -- Spin up a second Ubuntu box, install Docker, and register a remote agent. You will double your concurrency without touching the server.
- Set up scheduled pipelines -- Woodpecker's cron feature runs nightly builds, dependency updates, and security scans. Define them in the UI under each repository's settings.
- Front it with an identity provider -- If you want SSO beyond Git OAuth, place Authelia or Authentik in front of Woodpecker and require MFA for admin routes.
- Add a container registry -- Pair Woodpecker with a self-hosted registry (Harbor, Forgejo, or Gitea's built-in one) so your
buildandpublishsteps push into infrastructure you control end-to-end.
Want a pre-tuned VPS for CI workloads?>
Our CloudCore Professional plan gives you the 6 vCPU / 12 GB RAM / NVMe profile that Woodpecker, Gitea, and a couple of build agents love. Unmetered bandwidth means no surprise bill when your pipelines pull 50 GB of images a day.>
- 6 vCPU cores, 12 GB RAM, 100 GB NVMe
- Unmetered bandwidth
- EUR 19.99/month
- Ubuntu 24.04 LTS deployed in under a minute>
Deploy your CI VPS now and have Woodpecker building on every push by the end of your coffee break.