Self-Hosting Woodpecker CI: Lightweight Continuous Integration
Most self-hosted CI/CD tools are either overkill for a homelab or painful to configure. Jenkins needs a PhD in XML. GitLab CI requires GitLab. GitHub Actions is locked to GitHub’s cloud (or a heavyweight self-hosted runner setup).
Woodpecker CI takes a different approach: a lightweight, container-native CI/CD engine that uses ~100 MB of RAM for the server and ~30 MB per agent. It’s a community fork of Drone CI (before the license change), fully open source under Apache 2.0, and used in production by Codeberg — one of the largest community Git hosting platforms.
If you run Gitea, Forgejo, GitHub, GitLab, or Bitbucket and want CI/CD without the bloat, Woodpecker is worth a serious look.
Woodpecker vs Other CI/CD Tools
| Feature | Woodpecker CI | Drone CI | Gitea Actions | Jenkins |
|---|---|---|---|---|
| License | Apache 2.0 | Source Available | MIT (part of Gitea) | MIT |
| RAM Usage | ~130 MB total | ~200 MB | Part of Gitea | 1–4 GB |
| Config Format | YAML | YAML | GitHub Actions YAML | Groovy/XML |
| Container-Native | ✅ | ✅ | ✅ | Plugin-based |
| Multi-Forge Support | GitHub, Gitea, Forgejo, GitLab, Bitbucket | GitHub, Gitea, GitLab, Bitbucket | Gitea only | Any (plugins) |
| Plugin Ecosystem | Growing (100+) | Large | GitHub Actions compatible | Massive |
| Multi-Agent | ✅ | Enterprise only | N/A | ✅ |
| Kubernetes Backend | ✅ | ✅ | ❌ | ✅ |
| Web UI | Clean, modern | Clean | Integrated in Gitea | Dated |
| Complexity | Low | Low | Low (if using Gitea) | High |
Choose Woodpecker if you want a standalone, lightweight CI/CD engine that works with multiple forges. Choose Gitea Actions if you’re already on Gitea and want GitHub Actions compatibility. Choose Jenkins if you need hundreds of plugins and don’t mind the resource overhead.
Prerequisites
- A Linux server with Docker and Docker Compose installed
- A Git forge (GitHub, Gitea, Forgejo, GitLab, or Bitbucket) — you’ll need OAuth credentials
- A domain name pointed at your server (for webhooks and the web UI)
- Basic familiarity with Docker Compose and Git
Setting Up Woodpecker CI with Docker Compose
Woodpecker has two main components:
- Server — Web UI, API, webhook handler, pipeline scheduler
- Agent — Executes pipeline steps as Docker containers
Create a directory for your setup:
mkdir -p ~/woodpecker && cd ~/woodpecker
Generate a Shared Secret
The server and agent authenticate via a shared secret. Generate one:
openssl rand -hex 32
Save this value — you’ll use it in both the server and agent configuration.
Create the Environment File
Create a .env file to store sensitive configuration:
# .env
WOODPECKER_HOST=https://ci.yourdomain.com
WOODPECKER_AGENT_SECRET=your_generated_secret_here
# GitHub OAuth (replace with your forge's settings)
WOODPECKER_GITHUB=true
WOODPECKER_GITHUB_CLIENT=your_github_oauth_client_id
WOODPECKER_GITHUB_SECRET=your_github_oauth_client_secret
# Admin user(s) — comma-separated forge usernames
WOODPECKER_ADMIN=your_github_username
Create the Docker Compose File
# docker-compose.yml
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
container_name: woodpecker-server
restart: unless-stopped
ports:
- "8000:8000"
volumes:
- woodpecker-server-data:/var/lib/woodpecker/
environment:
- WOODPECKER_OPEN=${WOODPECKER_OPEN:-false}
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
# GitHub forge (swap for your forge — see Forge Configuration below)
- WOODPECKER_GITHUB=${WOODPECKER_GITHUB:-true}
- WOODPECKER_GITHUB_CLIENT=${WOODPECKER_GITHUB_CLIENT}
- WOODPECKER_GITHUB_SECRET=${WOODPECKER_GITHUB_SECRET}
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
container_name: woodpecker-agent
command: agent
restart: unless-stopped
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:
Start the stack:
docker compose up -d
The Woodpecker UI is now available at http://your-server-ip:8000. Log in with your forge account to activate repositories.
Forge Configuration
Woodpecker authenticates users and receives webhooks through your Git forge. Here’s how to configure the most popular options:
GitHub
- Go to Settings → Developer settings → OAuth Apps → New OAuth App
- Set Authorization callback URL to
https://ci.yourdomain.com/authorize - Copy the Client ID and Client Secret into your
.env
WOODPECKER_GITHUB=true
WOODPECKER_GITHUB_CLIENT=your_client_id
WOODPECKER_GITHUB_SECRET=your_client_secret
Gitea / Forgejo
- Go to Site Administration → Applications → Create a new OAuth2 Application (or user-level under Settings → Applications)
- Set Redirect URI to
https://ci.yourdomain.com/authorize
WOODPECKER_GITEA=true
WOODPECKER_GITEA_URL=https://gitea.yourdomain.com
WOODPECKER_GITEA_CLIENT=your_client_id
WOODPECKER_GITEA_SECRET=your_client_secret
For Forgejo, use the same variables but replace GITEA with FORGEJO:
WOODPECKER_FORGEJO=true
WOODPECKER_FORGEJO_URL=https://forgejo.yourdomain.com
WOODPECKER_FORGEJO_CLIENT=your_client_id
WOODPECKER_FORGEJO_SECRET=your_client_secret
GitLab
- Go to Admin Area → Applications → New Application
- Set Redirect URI to
https://ci.yourdomain.com/authorize - Select the
apiscope
WOODPECKER_GITLAB=true
WOODPECKER_GITLAB_URL=https://gitlab.yourdomain.com
WOODPECKER_GITLAB_CLIENT=your_client_id
WOODPECKER_GITLAB_SECRET=your_client_secret
Your First Pipeline
Once you’ve activated a repository in the Woodpecker UI, create a .woodpecker/ directory in your repo with a workflow file:
# .woodpecker/build.yaml
when:
- event: push
branch: main
steps:
- name: test
image: node:20-alpine
commands:
- npm ci
- npm test
- name: build
image: node:20-alpine
commands:
- npm run build
- name: notify
image: woodpeckerci/plugin-webhook
settings:
urls:
from_secret: webhook_url
content_type: application/json
when:
- status: [success, failure]
Push this file, and Woodpecker will automatically trigger the pipeline via the forge’s webhook.
Pipeline Syntax Highlights
- Steps run sequentially by default within a workflow
- Multiple workflows run in parallel — add separate YAML files to
.woodpecker/ - Services (databases, Redis) can be defined alongside steps
- Matrix builds let you test across versions
- Secrets are managed in the Woodpecker UI and referenced with
from_secret
Adding Services
Need a database for integration tests? Add a services section:
services:
- name: postgres
image: postgres:16-alpine
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
steps:
- name: test
image: node:20-alpine
environment:
- DATABASE_URL=postgres://test:test@postgres:5432/testdb
commands:
- npm ci
- npm run test:integration
Using the PostgreSQL Database Backend
For larger teams or high-volume pipelines, switch from SQLite to PostgreSQL:
# docker-compose.yml (updated)
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
container_name: woodpecker-server
restart: unless-stopped
ports:
- "8000:8000"
volumes:
- woodpecker-server-data:/var/lib/woodpecker/
environment:
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
- WOODPECKER_GITHUB=${WOODPECKER_GITHUB:-true}
- WOODPECKER_GITHUB_CLIENT=${WOODPECKER_GITHUB_CLIENT}
- WOODPECKER_GITHUB_SECRET=${WOODPECKER_GITHUB_SECRET}
- WOODPECKER_DATABASE_DRIVER=postgres
- WOODPECKER_DATABASE_DATASOURCE=postgres://woodpecker:secret@postgres:5432/woodpecker?sslmode=disable
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
container_name: woodpecker-db
restart: unless-stopped
volumes:
- woodpecker-db-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=woodpecker
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=woodpecker
healthcheck:
test: ["CMD-SHELL", "pg_isready -U woodpecker"]
interval: 10s
timeout: 5s
retries: 5
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
container_name: woodpecker-agent
command: agent
restart: unless-stopped
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-db-data:
woodpecker-agent-config:
Scaling with Multiple Agents
One of Woodpecker’s strengths is simple horizontal scaling. Add more agents on different machines to increase parallel build capacity:
# On a second machine
services:
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
command: agent
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=ci.yourdomain.com:9000
- WOODPECKER_AGENT_SECRET=same_secret_as_server
- WOODPECKER_MAX_WORKFLOWS=4
- WOODPECKER_GRPC_SECURE=true
- WOODPECKER_GRPC_VERIFY=true
When connecting agents over the internet, enable gRPC TLS with WOODPECKER_GRPC_SECURE=true. Expose port 9000 on the server and configure your reverse proxy to handle gRPC traffic (see reverse proxy section below).
Reverse Proxy Configuration
Caddy
Nginx
server {
listen 80;
server_name ci.yourdomain.com;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8000;
proxy_redirect off;
proxy_http_version 1.1;
proxy_buffering off;
chunked_transfer_encoding off;
}
}
For HTTPS with Nginx, add your SSL certificate configuration and a redirect block from port 80.
Secrets Management
Store sensitive values (API keys, deploy credentials) in Woodpecker’s built-in secrets manager:
- Navigate to your repository in the Woodpecker UI
- Go to Settings → Secrets → Add Secret
- Reference them in pipelines with
from_secret:
steps:
- name: deploy
image: alpine
environment:
- DEPLOY_TOKEN:
from_secret: deploy_token
commands:
- ./deploy.sh
Secrets can be scoped to specific events (push, pull request, tag) and images for security.
Organization-level secrets can be set via the CLI or API for values shared across multiple repositories:
docker exec woodpecker-server \
woodpecker-cli secret add \
--organization myorg \
--name docker_password \
--value "supersecret" \
--event push --event tag
Backup and Restore
SQLite (Default)
# Backup
docker compose stop woodpecker-server
docker run --rm -v woodpecker_woodpecker-server-data:/data -v $(pwd):/backup alpine \
tar czf /backup/woodpecker-backup-$(date +%Y%m%d).tar.gz -C /data .
docker compose start woodpecker-server
# Restore
docker compose stop woodpecker-server
docker run --rm -v woodpecker_woodpecker-server-data:/data -v $(pwd):/backup alpine \
sh -c "rm -rf /data/* && tar xzf /backup/woodpecker-backup-20260321.tar.gz -C /data"
docker compose start woodpecker-server
PostgreSQL
# Backup
docker exec woodpecker-db pg_dump -U woodpecker woodpecker > woodpecker-db-$(date +%Y%m%d).sql
# Restore
docker exec -i woodpecker-db psql -U woodpecker woodpecker < woodpecker-db-20260321.sql
Updating Woodpecker
cd ~/woodpecker
docker compose pull
docker compose up -d
Woodpecker handles database migrations automatically on startup. Check the release notes for any breaking changes before major version upgrades.
Troubleshooting
Agent Can’t Connect to Server
- Verify
WOODPECKER_AGENT_SECRETmatches on both server and agent - Check that port 9000 (gRPC) is accessible from the agent to the server
- For remote agents, enable
WOODPECKER_GRPC_SECURE=trueand ensure your reverse proxy handles gRPC correctly - Check agent logs:
docker logs woodpecker-agent
Webhooks Not Triggering
- Confirm
WOODPECKER_HOSTmatches your actual public URL (includinghttps://) - Check your forge’s webhook settings — the payload URL should be
https://ci.yourdomain.com/hook - Verify your firewall allows inbound connections from your forge
Pipeline Steps Failing with Permission Errors
- The agent needs access to the Docker socket: mount
/var/run/docker.sock - If using rootless Docker, configure the socket path accordingly
- Check that pipeline images can be pulled (network access, registry auth)
Out of Disk Space
- Old pipeline logs accumulate over time
- Clean up old Docker images:
docker system prune -af - Consider setting
WOODPECKER_LOG_STORE_MAX_AGEto auto-prune old logs
Slow Builds
- Increase
WOODPECKER_MAX_WORKFLOWSon the agent for more parallelism - Add more agents on separate machines
- Use lighter base images in your pipeline steps (Alpine variants)
- Leverage Docker layer caching by using consistent base images
Power User Tips
- Matrix builds — Test across multiple versions with
matrixin your workflow - Cron pipelines — Schedule periodic builds (nightly tests, dependency checks) from the Woodpecker UI under Repository Settings → Cron
- CLI tool —
woodpecker-clilets you manage repos, secrets, and pipelines from the terminal - Plugins — Browse woodpecker-ci.org/plugins for deployment, notification, and artifact plugins
- Local backend — Run steps directly on the host instead of Docker (useful for builds that need hardware access)
- Kubernetes backend — Scale agents across a k8s cluster for dynamic capacity
- Badge URLs — Add build status badges to your README:
https://ci.yourdomain.com/api/badges/owner/repo/status.svg - Conditional steps — Use
whenfilters on events, branches, paths, and status to control execution flow
Wrapping Up
Woodpecker CI hits a sweet spot for self-hosters: genuinely lightweight, straightforward YAML configuration, multi-forge support, and easy horizontal scaling. It won’t replace GitHub Actions for complex enterprise workflows, but for running tests, building containers, and deploying your projects — it’s hard to beat the simplicity-to-power ratio.
Start with the SQLite setup, connect your forge, and push a .woodpecker/ config to any repo. You’ll have CI/CD running in under 15 minutes.
Related guides: