Woodpecker CI: Self-Hosted CI/CD That Isn't Overcomplicated
You're self-hosting Gitea or Forgejo for your code. Now you want CI/CD — automated tests, builds, and deployments when you push code. You look at Jenkins and see a decade of accumulated complexity. You look at Gitea Actions and it's still maturing. You look at GitLab CI and realize you'd need to run all of GitLab.
Woodpecker CI is a community fork of Drone that's designed for self-hosters. It's simple, lightweight, and integrates natively with Gitea, Forgejo, GitHub, GitLab, and Bitbucket.
Why Woodpecker?
Woodpecker takes the "do one thing well" approach to CI/CD. It runs pipelines defined in YAML, executes them in Docker containers, and reports results back to your Git forge. That's it — no built-in artifact storage, no package registry, no issue tracker. Just CI/CD.
Woodpecker vs. Other Self-Hosted CI
| Feature | Woodpecker | Gitea Actions | Jenkins | Drone |
|---|---|---|---|---|
| Setup complexity | Low | Built into Gitea | High | Low |
| Resource usage | ~50 MB RAM | Part of Gitea | 500 MB+ RAM | ~50 MB |
| Pipeline syntax | Own YAML | GitHub Actions | Groovy/YAML | Own YAML |
| Plugin ecosystem | Good | GitHub Actions compat | Massive | Good |
| Container-native | Yes | Yes | Optional | Yes |
| Gitea/Forgejo support | Native | Built-in | Via plugin | Native |
| GitHub Actions compat | No | Yes | No | No |
| Active development | Very active | Active | Active | Slow |
| License | Apache 2.0 | MIT | MIT | Mixed (was proprietary) |
Choose Woodpecker if you want a simple, dedicated CI/CD server with clean YAML pipelines. Choose Gitea Actions if you're already on Gitea and want GitHub Actions compatibility. Choose Jenkins if you need enterprise features and can handle the complexity.
Setup with Docker
Server + Agent
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:latest
restart: unless-stopped
ports:
- 8000:8000
volumes:
- woodpecker_data:/var/lib/woodpecker
environment:
WOODPECKER_HOST: https://ci.yourdomain.com
WOODPECKER_OPEN: "false"
WOODPECKER_ADMIN: your-gitea-username
# Gitea integration
WOODPECKER_GITEA: "true"
WOODPECKER_GITEA_URL: https://git.yourdomain.com
WOODPECKER_GITEA_CLIENT: your-oauth-client-id
WOODPECKER_GITEA_SECRET: your-oauth-client-secret
# Agent communication
WOODPECKER_AGENT_SECRET: a-long-random-secret
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:latest
restart: unless-stopped
depends_on:
- woodpecker-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WOODPECKER_SERVER: woodpecker-server:9000
WOODPECKER_AGENT_SECRET: a-long-random-secret
WOODPECKER_MAX_WORKFLOWS: 2
volumes:
woodpecker_data:
Gitea/Forgejo OAuth Setup
- In Gitea: Settings → Applications → Create OAuth2 Application
- Name: Woodpecker CI
- Redirect URI:
https://ci.yourdomain.com/authorize - Copy the Client ID and Secret into the Woodpecker config
GitHub Integration
Replace the Gitea variables with:
WOODPECKER_GITHUB: "true"
WOODPECKER_GITHUB_CLIENT: your-github-oauth-client-id
WOODPECKER_GITHUB_SECRET: your-github-oauth-client-secret
Writing Pipelines
Create .woodpecker.yml (or .woodpecker/ directory) in your repository root.
Basic Pipeline
steps:
- name: test
image: node:20-alpine
commands:
- npm ci
- npm test
- name: lint
image: node:20-alpine
commands:
- npm ci
- npm run lint
Each step runs in a fresh container. Steps run sequentially by default.
Parallel Steps
steps:
- name: install
image: node:20-alpine
commands:
- npm ci
- name: test
image: node:20-alpine
commands:
- npm test
depends_on:
- install
- name: lint
image: node:20-alpine
commands:
- npm run lint
depends_on:
- install
test and lint run in parallel after install completes.
Multi-Pipeline
Split into files under .woodpecker/:
.woodpecker/
├── test.yml
├── lint.yml
└── deploy.yml
Each file is an independent pipeline that runs in parallel.
Conditional Steps
steps:
- name: deploy
image: alpine
commands:
- ./deploy.sh
when:
branch: main
event: push
- name: preview
image: alpine
commands:
- ./deploy-preview.sh
when:
event: pull_request
Services (Databases, etc.)
services:
- name: postgres
image: postgres:16-alpine
environment:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
steps:
- name: test
image: node:20-alpine
environment:
DATABASE_URL: postgres://test:test@postgres:5432/test
commands:
- npm ci
- npm run migrate
- npm test
Services are sidecar containers accessible by their name as a hostname.
Secrets
Add secrets through the Woodpecker UI (per-repo or organization-wide), then reference them:
steps:
- name: deploy
image: alpine
secrets: [deploy_key, server_host]
commands:
- ssh -i $DEPLOY_KEY user@$SERVER_HOST "./restart.sh"
when:
branch: main
event: push
Plugins
Woodpecker plugins are Docker images that accept configuration via environment variables. The ecosystem includes plugins for:
- Docker: Build and push images
- SSH: Deploy via SSH
- S3: Upload artifacts to S3/MinIO
- Slack/Discord: Send notifications
- Gitea Release: Create releases
- Cloudflare Pages: Deploy to CF Pages
Example: Docker Build and Push
steps:
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.yourdomain.com/myapp
registry: registry.yourdomain.com
username:
from_secret: registry_user
password:
from_secret: registry_password
tags:
- latest
- "${CI_COMMIT_SHA:0:8}"
when:
branch: main
event: push
Example: Notification
steps:
- name: notify
image: appleboy/drone-discord
settings:
webhook_id:
from_secret: discord_webhook_id
webhook_token:
from_secret: discord_webhook_token
message: "Build {{build.status}}: {{repo.name}} #{{build.number}}"
when:
status: [success, failure]
Scaling with Multiple Agents
Add more agents to handle concurrent builds:
# On server 2
services:
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:latest
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_FILTER_LABELS: "platform=linux/amd64"
Use labels to route specific pipelines to specific agents (e.g., ARM builds to a Raspberry Pi agent).
Matrix Builds
Test across multiple versions:
matrix:
NODE_VERSION:
- "18"
- "20"
- "22"
steps:
- name: test
image: "node:${NODE_VERSION}-alpine"
commands:
- node --version
- npm ci
- npm test
Cron Jobs
Woodpecker supports scheduled pipelines through its UI:
- Go to your repo in Woodpecker
- Settings → Cron
- Add a cron expression (e.g.,
0 2 * * *for 2 AM daily)
Use when: event: cron to only run certain steps on scheduled builds:
steps:
- name: security-scan
image: aquasec/trivy
commands:
- trivy fs --severity HIGH,CRITICAL .
when:
event: cron
cron: nightly-scan
Common Pitfalls
- Docker socket security: The agent needs Docker socket access to run containers. This is powerful — anyone with push access to a repo connected to Woodpecker can execute arbitrary containers. Limit which repos are activated.
- Secret exposure: Secrets are not available in pull request pipelines from forks by default (good). Don't change this setting.
- Agent resources: Each workflow step spawns a new Docker container. Ensure your agent host has enough CPU/RAM for concurrent builds.
- Pipeline caching: Woodpecker doesn't have built-in caching like GitHub Actions. Use a Docker volume or cache plugin for dependency caching between builds.
The Bottom Line
Woodpecker CI fills the "simple, self-hosted CI/CD" gap well. It's lightweight enough to run on a Raspberry Pi, flexible enough for real projects, and integrates cleanly with Gitea/Forgejo. The pipeline syntax is intuitive, the plugin ecosystem covers common use cases, and the server/agent architecture scales when you need it.
If you're self-hosting your Git forge and want CI/CD without the weight of Jenkins or the constraints of Gitea Actions, Woodpecker is the right choice.