The architecture

  • Server — receives webhooks from your Git forge, schedules pipelines, serves the web UI. SQLite by default; Postgres / MySQL for production.
  • Agent — runs the actual pipeline steps. One or more; each executes containers via Docker / Podman / Kubernetes / SSH / a local binary backend.
  • CLIwoodpecker-cli for triggering pipelines, fetching logs, managing secrets.

Install via docker compose

# docker-compose.yml
services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:latest
    container_name: woodpecker-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
      - "127.0.0.1:9000:9000"        # grpc for agents
    volumes:
      - ./server-data:/var/lib/woodpecker
    environment:
      WOODPECKER_OPEN: "false"        # require admin approval for new users
      WOODPECKER_ADMIN: "amir"
      WOODPECKER_HOST: https://ci.example.com
      WOODPECKER_AGENT_SECRET: ${AGENT_SECRET}

      # Gitea / Forgejo connection
      WOODPECKER_GITEA: "true"
      WOODPECKER_GITEA_URL: https://gitea.example.com
      WOODPECKER_GITEA_CLIENT: ${GITEA_OAUTH_CLIENT}
      WOODPECKER_GITEA_SECRET: ${GITEA_OAUTH_SECRET}

      WOODPECKER_DATABASE_DRIVER: postgres
      WOODPECKER_DATABASE_DATASOURCE: postgres://wood:${DB_PW}@db/woodpecker?sslmode=disable
    depends_on: [ db ]

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:latest
    container_name: woodpecker-agent
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WOODPECKER_SERVER: woodpecker-server:9000
      WOODPECKER_AGENT_SECRET: ${AGENT_SECRET}
      WOODPECKER_MAX_WORKFLOWS: 4

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: wood
      POSTGRES_PASSWORD: ${DB_PW}
      POSTGRES_DB: woodpecker
    volumes:
      - ./db-data:/var/lib/postgresql/data

Setup OAuth in Forgejo / Gitea

In Forgejo / Gitea Admin Panel → OAuth2 Applications, create a new app:

  • Name: Woodpecker CI
  • Redirect URI: https://ci.example.com/authorize
  • Save; copy Client ID + Client Secret to the env vars above
docker compose up -d
docker compose logs -f woodpecker-server

Browse to https://ci.example.com; log in via the Gitea OAuth flow; you're in.

The first pipeline

In any Git repo, add .woodpecker.yml (or .woodpecker/*.yml for multi-file pipelines):

# .woodpecker.yml
when:
  - event: push
    branch: [main, develop]
  - event: pull_request

steps:
  - name: install
    image: node:22-alpine
    commands:
      - npm ci

  - name: lint
    image: node:22-alpine
    commands:
      - npm run lint

  - name: test
    image: node:22-alpine
    commands:
      - npm test
    environment:
      CI: "true"

  - name: build
    image: node:22-alpine
    commands:
      - npm run build
    when:
      - event: push
        branch: main

  - name: docker
    image: woodpeckerci/plugin-docker-buildx
    settings:
      registry: ghcr.io
      repo: ghcr.io/myorg/myapp
      tags:
        - latest
        - ${CI_COMMIT_SHA}
      username:
        from_secret: ghcr_user
      password:
        from_secret: ghcr_token
    when:
      - event: push
        branch: main

Commit + push; Woodpecker picks up the webhook, schedules the pipeline. UI shows step-by-step logs in real time.

Services: long-running sidecars

For tests needing a database, Woodpecker's services spawn alongside the pipeline:

services:
  - name: postgres
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: test
      POSTGRES_DB: testdb

  - name: redis
    image: redis:7-alpine

steps:
  - name: test
    image: golang:1.23
    commands:
      - go test ./...
    environment:
      DATABASE_URL: postgres://postgres:test@postgres:5432/testdb
      REDIS_URL: redis://redis:6379

Services share a network with the pipeline steps; postgres / redis are reachable by their service name.

Plugins

Plugins are container images that take a settings block and perform a specific action: build + push Docker images, deploy to Kubernetes, send Slack notifications, upload to S3. Use them by image: ... + settings: { ... }. The Woodpecker community maintains a registry; you can write your own (any container that reads $PLUGIN_* env vars).

Secrets

# Via CLI
woodpecker-cli secret add --repository myorg/myapp \
    --name ghcr_token --value ghp_...

# Or in the web UI: Repository → Settings → Secrets

# Referenced in pipeline as
password:
  from_secret: ghcr_token

Per-repo, per-organization, or global secrets. Optional event filtering (only available on push, not on pull_request — protects fork PRs from accessing secrets).

Matrix builds

# Run the same steps across multiple Node versions
matrix:
  NODE_VERSION:
    - 20
    - 22
    - 23

steps:
  - name: test
    image: node:${NODE_VERSION}-alpine
    commands:
      - npm ci && npm test

Multi-pipeline workflows

For complex repos, split into multiple files under .woodpecker/:

.woodpecker/
    01-lint.yml
    02-test.yml
    03-build.yml
    04-deploy.yml

Each file is an independent pipeline; declare dependencies with depends_on:. Allows parallel + sequential composition.

Multiple agents + Kubernetes backend

For more parallelism, run more agents (each handles N pipelines simultaneously based on WOODPECKER_MAX_WORKFLOWS). For very large fleets, use the Kubernetes backend — each step runs as a pod, scheduled across cluster nodes.

# Agent env for K8s backend
WOODPECKER_BACKEND: kubernetes
WOODPECKER_BACKEND_K8S_NAMESPACE: woodpecker
WOODPECKER_BACKEND_K8S_STORAGE_CLASS: standard

Woodpecker vs alternatives

  • Drone CI — Woodpecker is the community fork after Drone's restrictive license changes. Same YAML format; same plugin ecosystem. Woodpecker has been ahead of Drone on features for 2+ years.
  • GitHub Actions (with self-hosted runners) — bigger ecosystem; tighter GitHub integration. Heavier; the act-runner project lets self-hosted GitHub Actions run on Forgejo / Gitea too.
  • GitLab CI — bundled with GitLab; great if you're on GitLab; not portable.
  • Jenkins — the elder; vastly more feature surface; correspondingly heavier and crustier. Don't pick for new self-hosted setups in 2026.
  • Forgejo Actions — Forgejo's bundled CI, GitHub Actions-compatible YAML. The right pick if you already run Forgejo and want zero extra services.

For "small team, self-hosted Forgejo / Gitea, wants real CI without operational drama," Woodpecker is the right size in 2026.