Woodpecker CI: Lightweight Drone CI Fork for Homelabs
Community-driven CI/CD with Docker-first design. The recommended Drone CI replacement for 2025.
Overview
Woodpecker CI is a community-driven fork of Drone CI that emerged when Drone shifted to an enterprise-focused model. Built for cloud-native workflows, Woodpecker runs every job in isolated Docker containers, ensuring consistency and reproducibility while keeping resource usage minimal—perfect for homelab environments.
With a focus on simplicity and container-first design, Woodpecker brings enterprise CI/CD patterns to resource-constrained environments. Each pipeline runs in its own container, builds are completely isolated, and the entire system runs smoothly on 1-2GB of RAM.
What you’ll learn:
- Installing Woodpecker server and agents
- Writing pipeline configuration files
- Integrating with Gitea, GitHub, and GitLab
- Building and publishing Docker images
- Using plugins for common CI/CD tasks
- Managing secrets and environment variables
- Multi-platform builds (amd64, arm64)
- Deploying applications from pipelines
Why Woodpecker CI for Homelabs?
Every build runs in a fresh container—no dependency pollution, no “it works on my machine” problems. Use the same Docker image for your dev environment and CI builds. Scale horizontally by adding agent containers. Deploy to Kubernetes, Docker Swarm, or bare metal with the same simple pipeline syntax.
Prerequisites
Before starting, ensure you have:
- Docker and Docker Compose installed
- Git forge (Gitea, GitHub, GitLab, or Bitbucket)
- Basic Docker knowledge (images, containers, volumes)
- Hardware requirements:
- Server: 512MB RAM, 5GB storage
- Agent: 1GB RAM per concurrent build
- Network: Port 8000 (UI), port 9000 (gRPC for agents)
Optional but helpful:
- Familiarity with YAML
- Experience with CI/CD concepts
- Basic understanding of Docker registries
Understanding Woodpecker Architecture
Component Overview
Woodpecker Server:
- Web UI for pipeline visualization
- Git forge integration (webhooks)
- Job scheduling and queuing
- Secret management
- User authentication
Woodpecker Agent:
- Executes pipeline steps
- Runs each step in Docker container
- Reports status back to server
- Can run on same host or distributed
- Auto-scales based on load
Pipeline Flow:
[Git Push] → [Forge webhook] → [Woodpecker Server]
↓
[Queues pipeline job]
↓
[Woodpecker Agent] ← [Polls for jobs] ← [Server assigns job]
↓
[Pulls Docker images for steps]
↓
[Runs pipeline steps in containers]
↓
[Reports results] → [Server] → [Updates UI] Woodpecker vs. Other CI Tools
| Feature | Woodpecker CI | Gitea Actions | GitLab CI |
|---|---|---|---|
| RAM Usage | 512MB+ | 512MB+ | 4-8GB |
| Container-Native | Yes (required) | Optional | Optional |
| Pipeline Syntax | YAML (.woodpecker.yml) | GitHub Actions | GitLab CI |
| Forge Integration | Gitea, GitHub, GitLab, etc. | Gitea only | GitLab only |
| Plugin Ecosystem | Drone plugins | GitHub Actions | GitLab plugins |
| Multi-Arch Builds | Built-in | Via Docker Buildx | Via Docker |
| Secrets Management | Built-in | Built-in | Built-in |
| Best For | Container workflows | GitHub Actions users | GitLab users |
Installation with Docker Compose
Basic Installation (Gitea Integration)
Create docker-compose.yml:
version: '3.8'
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:latest
container_name: woodpecker-server
restart: unless-stopped
ports:
- "8000:8000"
- "9000:9000"
volumes:
- woodpecker-server-data:/var/lib/woodpecker/
environment:
- WOODPECKER_OPEN=true
- WOODPECKER_HOST=http://ci.homelab.local:8000
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=http://gitea.homelab.local:3000
- WOODPECKER_GITEA_CLIENT=${GITEA_CLIENT_ID}
- WOODPECKER_GITEA_SECRET=${GITEA_CLIENT_SECRET}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN_USER}
networks:
- woodpecker
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
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=2
networks:
- woodpecker
volumes:
woodpecker-server-data:
networks:
woodpecker:
driver: bridge Configuration Steps
1. Create OAuth2 application in Gitea:
- Log into Gitea
- Settings → Applications → Manage OAuth2 Applications
- Click Create a new OAuth2 Application
- Configure:
- Application Name: Woodpecker CI
- Redirect URI:
http://ci.homelab.local:8000/authorize
- Click Create Application
- Save Client ID and Client Secret
2. Generate agent secret:
openssl rand -hex 32 3. Create .env file:
GITEA_CLIENT_ID=your-client-id-here
GITEA_CLIENT_SECRET=your-client-secret-here
WOODPECKER_AGENT_SECRET=your-generated-secret-here
WOODPECKER_ADMIN_USER=your-gitea-username 4. Start Woodpecker:
docker-compose up -d
docker-compose logs -f 5. Access Woodpecker UI:
Navigate to http://ci.homelab.local:8000 (or http://YOUR-SERVER-IP:8000)
6. Authorize with Gitea:
- Click Login
- Authorize Woodpecker to access Gitea
- You’ll be redirected to Woodpecker dashboard
Your First Pipeline
Creating a Repository
- Create new repository in Gitea:
hello-woodpecker - Clone locally
- Add application code and pipeline file
Writing a Basic Pipeline
Create .woodpecker.yml in repository root:
# .woodpecker.yml
when:
- event: push
branch: main
steps:
hello:
image: alpine:latest
commands:
- echo "Hello from Woodpecker CI!"
- echo "Current directory: $(pwd)"
- ls -la
- uname -a
show-env:
image: alpine:latest
commands:
- env | sort
- echo "Woodpecker version: ${CI_WOODPECKER_VERSION}"
- echo "Repository: ${CI_REPO}"
- echo "Branch: ${CI_COMMIT_BRANCH}" Push to trigger pipeline:
git add .woodpecker.yml
git commit -m "Add Woodpecker pipeline"
git push origin main Watch pipeline execute:
- Go to Woodpecker UI
- Repository should appear (click Repositories → Sync)
- Enable repository
- View pipeline execution
- Click on build to see step logs
Pipeline Structure Explained
# Global settings
when: # When to run pipeline
- event: push # On push events
branch: main # Only main branch
steps: # Pipeline steps
step-name: # Step identifier
image: alpine:latest # Docker image to use
commands: # Commands to run
- echo "Hello"
- ./script.sh
another-step:
image: node:18
commands:
- npm install
- npm test Available Events
when:
# Push to repository
- event: push
branch: [main, develop]
# Pull request opened/updated
- event: pull_request
# Tag created
- event: tag
# Manual trigger
- event: manual
# Cron schedule
- event: cron
cron: nightly
# Deployment event
- event: deployment
environment: production Building and Testing Applications
Node.js Application Pipeline
when:
- event: [push, pull_request]
steps:
install:
image: node:18-alpine
commands:
- npm ci
lint:
image: node:18-alpine
commands:
- npm run lint
test:
image: node:18-alpine
commands:
- npm test
environment:
- NODE_ENV=test
build:
image: node:18-alpine
commands:
- npm run build
when:
- branch: main
event: push Python Application with Dependencies
steps:
restore-cache:
image: meltwater/drone-cache:latest
settings:
backend: "filesystem"
restore: true
cache_key: "{{ .Repo.Name }}-{{ checksum "requirements.txt" }}"
archive_format: "gzip"
mount:
- '.venv'
install:
image: python:3.11-alpine
commands:
- python -m venv .venv
- source .venv/bin/activate
- pip install -r requirements.txt
test:
image: python:3.11-alpine
commands:
- source .venv/bin/activate
- pytest --cov=./ --cov-report=xml
rebuild-cache:
image: meltwater/drone-cache:latest
settings:
backend: "filesystem"
rebuild: true
cache_key: "{{ .Repo.Name }}-{{ checksum "requirements.txt" }}"
archive_format: "gzip"
mount:
- '.venv' Go Application Build
steps:
build:
image: golang:1.21-alpine
commands:
- go mod download
- go build -o app .
- ./app --version
test:
image: golang:1.21-alpine
commands:
- go test -v -race ./...
lint:
image: golangci/golangci-lint:latest
commands:
- golangci-lint run
compile-multi-arch:
image: golang:1.21-alpine
commands:
- GOOS=linux GOARCH=amd64 go build -o app-amd64
- GOOS=linux GOARCH=arm64 go build -o app-arm64 Docker Image Building
Basic Docker Build and Push
when:
- event: push
branch: main
steps:
build:
image: woodpeckerci/plugin-docker-buildx:latest
settings:
repo: registry.homelab.local/myapp
registry: registry.homelab.local
tags:
- latest
- ${CI_COMMIT_SHA:0:8}
username:
from_secret: docker_username
password:
from_secret: docker_password Multi-Platform Docker Builds
steps:
build-multiarch:
image: woodpeckerci/plugin-docker-buildx:latest
settings:
repo: registry.homelab.local/myapp
registry: registry.homelab.local
platforms:
- linux/amd64
- linux/arm64
- linux/arm/v7
tags:
- latest
- ${CI_COMMIT_TAG}
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
- event: tag Build with BuildKit Caching
steps:
docker-build:
image: woodpeckerci/plugin-docker-buildx:latest
settings:
repo: registry.homelab.local/myapp
registry: registry.homelab.local
tags: ${CI_COMMIT_SHA}
cache_from: registry.homelab.local/myapp:buildcache
cache_to: type=registry,ref=registry.homelab.local/myapp:buildcache,mode=max
build_args:
- VERSION=${CI_COMMIT_TAG}
dockerfile: Dockerfile
context: .
username:
from_secret: docker_username
password:
from_secret: docker_password Using Plugins
Popular Woodpecker Plugins
Docker plugin (build & push images):
steps:
docker:
image: woodpeckerci/plugin-docker-buildx:latest
settings:
repo: myregistry/myapp
tags: latest Git plugin (clone with credentials):
steps:
clone:
image: woodpeckerci/plugin-git:latest
settings:
depth: 50
recursive: true
tags: true SSH plugin (deploy via SSH):
steps:
deploy:
image: appleboy/drone-ssh:latest
settings:
host: server.homelab.local
username: deploy
key:
from_secret: ssh_private_key
script:
- cd /opt/app
- docker-compose pull
- docker-compose up -d Telegram notification plugin:
steps:
notify:
image: appleboy/drone-telegram:latest
settings:
token:
from_secret: telegram_token
to:
from_secret: telegram_chat_id
message: >
✅ Build #${CI_BUILD_NUMBER} succeeded!
📦 ${CI_REPO}
🔗 ${CI_BUILD_LINK}
when:
- status: success
notify-failure:
image: appleboy/drone-telegram:latest
settings:
token:
from_secret: telegram_token
to:
from_secret: telegram_chat_id
message: >
❌ Build #${CI_BUILD_NUMBER} failed!
📦 ${CI_REPO}
🔗 ${CI_BUILD_LINK}
when:
- status: failure Secrets Management
Adding Secrets via UI
- Go to repository in Woodpecker UI
- Click Settings → Secrets
- Click Add Secret
- Configure:
- Name:
docker_password - Value:
your-secret-value - Events: push, tag (select which events can access)
- Name:
- Click Add
Using Secrets in Pipelines
steps:
deploy:
image: alpine:latest
environment:
- API_KEY=
from_secret: api_key
- DB_PASSWORD=
from_secret: db_password
commands:
- echo "Deploying with API key: ${API_KEY}"
- ./deploy.sh Global Secrets (Organization-Level)
- Woodpecker UI → Admin → Secrets
- Add global secrets available to all repositories
- Useful for shared credentials (Docker registry, SSH keys)
Secret Security Best Practices
Never log secrets:
steps:
deploy:
image: alpine:latest
environment:
- TOKEN=
from_secret: deploy_token
commands:
# ❌ BAD - logs secret
- echo "Token: $TOKEN"
# ✅ GOOD - use without logging
- curl -H "Authorization: Bearer $TOKEN" api.example.com Use separate secrets per environment:
steps:
deploy-dev:
environment:
- API_KEY=
from_secret: dev_api_key
when:
- branch: develop
deploy-prod:
environment:
- API_KEY=
from_secret: prod_api_key
when:
- branch: main Advanced Pipeline Patterns
Matrix Builds
Test across multiple versions:
matrix:
- NODE_VERSION: 16
- NODE_VERSION: 18
- NODE_VERSION: 20
steps:
test:
image: node:${NODE_VERSION}-alpine
commands:
- npm ci
- npm test Parallel Steps
Run steps concurrently:
steps:
test-unit:
image: node:18
commands:
- npm run test:unit
group: testing
test-integration:
image: node:18
commands:
- npm run test:integration
group: testing
test-e2e:
image: node:18
commands:
- npm run test:e2e
group: testing
# This step waits for all 'testing' group steps to complete
deploy:
image: alpine
commands:
- ./deploy.sh Conditional Steps
steps:
build:
image: golang:1.21
commands:
- go build
deploy-staging:
image: alpine
commands:
- ./deploy-staging.sh
when:
- branch: develop
event: push
deploy-production:
image: alpine
commands:
- ./deploy-production.sh
when:
- branch: main
event: push
status: success Services (Database Containers)
Run database for integration tests:
services:
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
redis:
image: redis:7-alpine
steps:
integration-tests:
image: node:18
environment:
- DATABASE_URL=postgres://test:test@postgres:5432/testdb
- REDIS_URL=redis://redis:6379
commands:
- npm run test:integration Deployment Workflows
Deploy to Kubernetes
when:
- event: push
branch: main
steps:
build-image:
image: woodpeckerci/plugin-docker-buildx:latest
settings:
repo: registry.homelab.local/myapp
tags: ${CI_COMMIT_SHA}
username:
from_secret: docker_username
password:
from_secret: docker_password
deploy-k8s:
image: bitnami/kubectl:latest
environment:
- KUBECONFIG=/tmp/kubeconfig
- K8S_CONFIG=
from_secret: kubeconfig
commands:
- echo "$K8S_CONFIG" > /tmp/kubeconfig
- kubectl set image deployment/myapp myapp=registry.homelab.local/myapp:${CI_COMMIT_SHA}
- kubectl rollout status deployment/myapp Deploy to Docker Swarm
steps:
deploy-swarm:
image: alpine:latest
environment:
- DOCKER_HOST=tcp://swarm-manager.local:2376
- DOCKER_TLS_VERIFY=1
- DOCKER_CERT_PATH=/certs
secrets: [docker_cert, docker_key, docker_ca]
commands:
- apk add --no-cache docker-cli
- docker stack deploy -c docker-compose.yml myapp Blue-Green Deployment
steps:
deploy-blue:
image: alpine
commands:
- ssh deploy@server "deploy-blue.sh ${CI_COMMIT_SHA}"
- ssh deploy@server "health-check-blue.sh"
when:
- event: deployment
environment: production
switch-traffic:
image: alpine
commands:
- ssh deploy@server "switch-to-blue.sh"
when:
- status: success Scaling and Multi-Agent Setup
Adding Additional Agents
Scale horizontally by running multiple agents:
# docker-compose.yml
services:
woodpecker-agent-1:
image: woodpeckerci/woodpecker-agent:latest
# ... config
woodpecker-agent-2:
image: woodpeckerci/woodpecker-agent:latest
# ... same config as agent-1
woodpecker-agent-3:
image: woodpeckerci/woodpecker-agent:latest
# ... same config Or use Docker Compose scale:
docker-compose up -d --scale woodpecker-agent=3 Agent Labels for Specialized Builds
Configure agents with labels:
# Agent 1 (x86_64, GPU)
woodpecker-agent-gpu:
image: woodpeckerci/woodpecker-agent:latest
environment:
- WOODPECKER_FILTER_LABELS=arch=amd64,gpu=nvidia
# Agent 2 (ARM64)
woodpecker-agent-arm:
image: woodpeckerci/woodpecker-agent:latest
environment:
- WOODPECKER_FILTER_LABELS=arch=arm64 Use labels in pipelines:
steps:
build-amd64:
image: golang:1.21
commands:
- GOARCH=amd64 go build
labels:
arch: amd64
build-arm64:
image: golang:1.21
commands:
- GOARCH=arm64 go build
labels:
arch: arm64
build-with-gpu:
image: tensorflow/tensorflow:latest-gpu
commands:
- python train.py
labels:
gpu: nvidia Monitoring and Troubleshooting
Viewing Logs
Server logs:
docker logs woodpecker-server
docker logs -f woodpecker-server --tail 100 Agent logs:
docker logs woodpecker-agent Common log filters:
# Errors only
docker logs woodpecker-server 2>&1 | grep -i error
# Specific repository
docker logs woodpecker-server 2>&1 | grep "repo-name" Common Issues
Agent not connecting:
# Verify agent secret matches server
docker exec woodpecker-agent env | grep WOODPECKER_AGENT_SECRET
# Check network connectivity
docker exec woodpecker-agent ping woodpecker-server Pipeline not triggering:
- Check repository is enabled in Woodpecker UI
- Verify webhook exists in Gitea (Settings → Webhooks)
- Check webhook recent deliveries for errors
- Ensure
.woodpecker.ymlsyntax is valid
Pipeline fails immediately:
# Check 'when' conditions aren't blocking
when:
- event: push # Matches your event?
branch: main # Matches your branch?
# Verify Docker image exists
steps:
test:
image: nonexistent:latest # ❌ Will fail Comparison: Drone CI vs. Woodpecker CI
Why Woodpecker Forked from Drone
Drone 1.x → 2.x changes:
- Moved to proprietary enterprise model
- Removed features from free tier
- Less community involvement
- License changes
Woodpecker advantages:
- Fully open-source (Apache 2.0 license)
- Community-driven development
- All features available (no enterprise-only features)
- Active development and updates
Migration from Drone
Pipeline compatibility:
- Woodpecker uses
.woodpecker.yml(instead of.drone.yml) - Syntax is 95% compatible
- Most Drone plugins work unchanged
Migration steps:
# Rename pipeline file
mv .drone.yml .woodpecker.yml
# Update environment variables (if referencing CI variables)
# DRONE_REPO → CI_REPO
# DRONE_COMMIT → CI_COMMIT_SHA Conclusion and Next Steps
Woodpecker CI delivers container-native CI/CD with minimal overhead, making it ideal for homelabs and resource-constrained environments. You’ve learned:
- Installing and configuring Woodpecker server and agents
- Writing pipeline configurations
- Building and deploying Docker images
- Using plugins for extended functionality
- Managing secrets and environment variables
- Scaling with multiple agents
Key Takeaways
- Container-first design ensures build reproducibility
- Low resource usage (1-2GB RAM total)
- Drone plugin compatibility provides rich ecosystem
- Horizontal scaling with multiple agents
- Multi-platform builds built-in
Recommended Next Steps
- Deploy Woodpecker in your homelab today
- Create your first pipeline for an existing project
- Set up Docker image building for your apps
- Configure deployment pipelines to your infrastructure
- Explore plugin ecosystem for advanced functionality
Related Guides
- Gitea Actions - Alternative CI/CD for Gitea
- Docker Registry - Host container images locally
- Kubernetes with K3s - Deploy to K8s clusters
- Ansible - Complement CI with configuration management
Resources
- Official Documentation: woodpecker-ci.org/docs
- GitHub: github.com/woodpecker-ci/woodpecker
- Community Chat: discord.woodpecker-ci.org
- Plugin Index: woodpecker-ci.org/plugins
Document Version: 1.0 Last Updated: December 16, 2025 Based on: Woodpecker CI 2.3+ Estimated Completion Time: 60 minutes
Happy building!