Automation intermediate ⏱ 45 minutes

Woodpecker CI: Lightweight Drone CI Fork for Homelabs

Community-driven CI/CD with Docker-first design. The recommended Drone CI replacement for 2025.

By homelabbing.io • 2025-12-16
#woodpecker#ci-cd#drone#devops#docker

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

FeatureWoodpecker CIGitea ActionsGitLab CI
RAM Usage512MB+512MB+4-8GB
Container-NativeYes (required)OptionalOptional
Pipeline SyntaxYAML (.woodpecker.yml)GitHub ActionsGitLab CI
Forge IntegrationGitea, GitHub, GitLab, etc.Gitea onlyGitLab only
Plugin EcosystemDrone pluginsGitHub ActionsGitLab plugins
Multi-Arch BuildsBuilt-inVia Docker BuildxVia Docker
Secrets ManagementBuilt-inBuilt-inBuilt-in
Best ForContainer workflowsGitHub Actions usersGitLab 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:

  1. Log into Gitea
  2. Settings → Applications → Manage OAuth2 Applications
  3. Click Create a new OAuth2 Application
  4. Configure:
    • Application Name: Woodpecker CI
    • Redirect URI: http://ci.homelab.local:8000/authorize
  5. Click Create Application
  6. 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:

  1. Click Login
  2. Authorize Woodpecker to access Gitea
  3. You’ll be redirected to Woodpecker dashboard

Your First Pipeline

Creating a Repository

  1. Create new repository in Gitea: hello-woodpecker
  2. Clone locally
  3. 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:

  1. Go to Woodpecker UI
  2. Repository should appear (click Repositories → Sync)
  3. Enable repository
  4. View pipeline execution
  5. 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

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

  1. Go to repository in Woodpecker UI
  2. Click Settings → Secrets
  3. Click Add Secret
  4. Configure:
    • Name: docker_password
    • Value: your-secret-value
    • Events: push, tag (select which events can access)
  5. 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)

  1. Woodpecker UI → Admin → Secrets
  2. Add global secrets available to all repositories
  3. 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:

  1. Check repository is enabled in Woodpecker UI
  2. Verify webhook exists in Gitea (Settings → Webhooks)
  3. Check webhook recent deliveries for errors
  4. Ensure .woodpecker.yml syntax 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
  1. Deploy Woodpecker in your homelab today
  2. Create your first pipeline for an existing project
  3. Set up Docker image building for your apps
  4. Configure deployment pipelines to your infrastructure
  5. Explore plugin ecosystem for advanced functionality
  • 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


Document Version: 1.0 Last Updated: December 16, 2025 Based on: Woodpecker CI 2.3+ Estimated Completion Time: 60 minutes

Happy building!