mcpField
W
writing-woodpecker-ci

writing-woodpecker-ci

github.com/beecode-rsCloud & DevOps049 installsUpdated 1w ago
Repository

About writing-woodpecker-ci

A skill for CI/CD engineers migrating GitHub Actions or Semaphore CI pipelines to Woodpecker CI. It explains Docker-based step configuration, workflow dependencies, and key syntax differences.

Key features

  1. 01GitHub Actions 迁移对照
  2. 02Semaphore CI 迁移对照
  3. 03Woodpecker YAML 语法指南
  4. 04工作流依赖与并行配置
  5. 05矩阵、服务与缓存设置

Use cases

  1. 01将 GitHub Actions 工作流迁移到 Woodpecker CI
  2. 02将 Semaphore CI 流水线转换为 Woodpecker CI
  3. 03配置多文件工作流依赖和并行运行

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/beecode-rs/claude-skills --skill writing-woodpecker-ci

After installing, type/writing-woodpecker-ciin Claude Code to confirm it loaded.

Bundled filesLICENSE.txt

Details

From the project README

Woodpecker CI Pipeline Migration Expert

You are an expert CI/CD engineer specialized in migrating pipelines from GitHub Actions and Semaphore CI to Woodpecker CI. Woodpecker is a Docker-first, community-driven CI engine (forked from Drone) that runs every pipeline step inside an isolated container.

Understanding this Docker-native philosophy is the key to good migrations: there are no "runners" or "agents" to configure — you specify a Docker image per step, and everything else follows from that.


Anatomy of .woodpecker.yml

steps:
  build-step-name:
    image: alpine:latest      # REQUIRED: Docker image for this step
    commands:                 # Shell commands to run inside the container
      - echo "Hello World"
    environment:              # Step-level env vars
      FOO: bar
    secrets: [github_token]   # Injected from Woodpecker UI/CLI secrets
    when:                     # Conditional execution
      - event: push
        branch: main

  plugin-step-name:
    image: woodpeckerci/plugin-docker-buildx:latest
    settings:                 # Passed as PLUGIN_-prefixed env vars to the plugin
      repo: myorg/myrepo
      tags: latest

Key things to remember:

  • Every step needs an image: — there is no shared runner environment
  • commands: runs shell inside the container — standard Linux tools available
  • settings: is for plugins only — it becomes PLUGIN_* env vars automatically
  • Woodpecker clones the repo by default — no checkout step needed (unlike GHA)
  • Steps can also be defined as a dictionary (name as key) or as a list with - name: — both are valid

File Organization

Single file (simple projects)

Place .woodpecker.yml at the repo root. Woodpecker creates one pipeline with one workflow.

Multi-file (recommended for real projects)

Place YAML files in .woodpecker/ directory — each file becomes a separate workflow that runs on its own agent in parallel:

.woodpecker/
├── build.yaml
├── test.yaml       # depends_on: [build]
├── lint.yaml       # runs in parallel with build
└── deploy.yaml     # depends_on: [lint, build, test]

Each workflow reports its own status to the forge (GitHub/Gitea/etc.), so you get faster feedback — lint can fail while tests are still running.

Critical gotcha: Files are only shared between steps of the same workflow. If build.yaml produces an artifact that deploy.yaml needs, use a storage plugin (e.g., S3) to pass it between workflows. This is different from GHA where artifacts can be passed between jobs.


Migration Cheatsheets

GitHub Actions → Woodpecker CI
GitHub ActionsWoodpecker CINotes
.github/workflows/*.yml.woodpecker.yml or .woodpecker/*.yamlSingle file or directory of workflows
on: [push, pull_request]when: per-step or per-workflowConditions evaluated per step, not per pipeline
jobs.<id>.steps[]steps:Direct mapping
runs-on: ubuntu-latest(removed)No runners — just specify image: per step
uses: actions/checkout@v3(usually not needed)Woodpecker auto-clones; use clone: block for special cases
uses: action/repo@v1image: plugin/repo:latestGHA Actions are incompatible — find a Woodpecker plugin or rewrite as commands
with: (action inputs)settings:Only for plugins; becomes PLUGIN_* env vars
env:environment:Works at step or pipeline level
secrets: GH_TOKENsecrets: [gh_token]Auto-injected as env vars
if: github.ref == 'main'when: [branch: main]See the when syntax below
needs: [build]depends_on: [build]Works at workflow level (between files) and step level (within a workflow)
strategy.matrixmatrix:Matrix variables injected as env vars
services:services:Linked Docker containers, hostname = service name
actions/cachevolumes:Mount host paths for persistence between steps
continue-on-error: truefailure: ignoreStep can fail without failing the workflow
[skip ci] in commit[SKIP CI] or [CI SKIP]Case-insensitive
Semaphore CI → Woodpecker CI
Semaphore CIWoodpecker CINotes
.semaphore/semaphore.yml.woodpecker.yml
blocks:steps:Semaphore blocks map to Woodpecker steps
task:steps.[name]:
agent.machine.type:(removed)Handled by Woodpecker agent config on the server
prologue: / epilogue:First/last stepsAdd a step named setup or teardown
promotions:workflows: with depends_onDAG-based deployment pipelines
secrets: (in config)secrets: [name]Managed via UI/CLI, referenced by name

Syntax Reference

The when Block (Conditions)

Woodpecker uses logical AND within a list item, logical OR between list items. This applies both at the step level and at the global workflow level (to skip entire workflows).

Available events: push, pull_request, pull_request_closed, pull_request_metadata, tag, release, deployment, cron, manual

when:
  # Run on push to main OR dev
  - event: push
    branch: [main, dev]
  # Run on pull requests to main
  - event: pull_request
    branch: main
  # Only on specific cron job
  - event: cron
    cron: sync_*            # glob pattern on cron job name
  # Only for tags starting with 'v'
  - event: tag
    ref: refs/tags/v*
  # Path filtering (only on push and pull_request events)
  - event: push
    path:
      include: ['src/**/*.go', 'go.mod']
      exclude: ['**_test.go']
      ignore_message: '[ALL]'   # skip path filter if commit msg contains this
      on_empty: true            # treat empty file list as match (default: true)
  # Run even on failure (e.g., for notifications)
  - status: [failure]
  # Custom expression evaluation
  - evaluate: 'CI_COMMIT_AUTHOR == "woodpecker-ci"'

More when filters:

  • repo: owner/name — only for specific repository
  • platform: linux/amd64 — only on specific agent platform (use with matrix builds)
  • instance: ci.example.com — only on specific Woodpecker server
  • matrix: { GO_VERSION: 1.5 } — only for specific matrix permutation

Global workflow when — skip an entire workflow based on conditions. Place at the top level, before steps::

when:
  branch: main

steps:
  - name: deploy
    image: alpine
    commands: echo "deploying..."
Workflows (DAG / Parallelism)

Between workflows (multi-file): Place files in .woodpecker/, use depends_on at the top level. The name for depends_on is the filename without extension:

# .woodpecker/deploy.yaml
depends_on:
  - lint
  - build
  - test

steps:
  - name: deploy
    image: alpine
    commands: echo "deploying..."

To run a workflow even when its dependencies failed:

depends_on:
  - deploy
runs_on: [success, failure]   # default is [success] only

Between steps within a single workflow — use depends_on on individual steps to create a DAG. Once you set depends_on on any step, all other steps without it run immediately in parallel:

steps:
  - name: build       # runs immediately
    image: golang:1.21
    commands: go build ./...

  - name: test         # runs immediately (no depends_on)
    image: golang:1.21
    commands: go test ./...

  - name: deploy       # waits for both build AND test
    image: alpine
    commands: echo "deploying..."
    depends_on: [build, test]

  - name: check-format # runs immediately (empty depends_on forces parallel)
    image: mstruebing/editorconfig-checker
    depends_on: []
Step Options
steps:
  - name: build
    image: golang:1.21
    pull: true              # always pull latest image (default: only if not present)
    commands:
      - go build
      - go test
    environment:            # step-level env vars (can't expand variables here)
      CGO: 0
      GOOS: linux
    failure: ignore         # allow failure without failing workflow (like continue-on-error)
    # failure: cancel       # alternative: cancel the whole pipeline on failure
    directory: ./subdir     # run commands in a subdirectory
    when:
      - event: push
    # detach: true          # run step in background until workflow finishes
    # privileged: true      # escalated capabilities (trusted repos only)
    # backend_options:      # backend-specific options (Docker user/group, K8s service account)
    #   kubernetes:
    #     serviceAccountName: my-sa
Clone Customization

Woodpecker auto-clones the repo. Override or skip it when needed:

# Skip cloning entirely (e.g., notification-only workflows)
skip_clone: true

# Customize clone behavior
clone:
  git:
    image: woodpeckerci/plugin-git
    settings:
      depth: 50
      partial: false

# Clone submodules with SSH-to-HTTPS override
clone:
  git:
    image: woodpeckerci/plugin-git
    settings:
      recursive: true
      submodule_override:
        my-module: https://github.com/octocat/my-module.git
Workspace

All steps in a workflow share the same workspace (a mounted volume). Default path: /woodpecker/src/{host}/{owner}/{repo}.

workspace:
  base: /go
  path: src/github.com/octocat/hello-world

steps:
  - name: build
    image: golang:latest
    commands:
      - go get
      - go build
Services (Databases, etc.)
services:
  database:
    image: postgres:15
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test

steps:
  - name: test
    image: alpine
    commands:
      - ping -c 1 database  # Hostname = service name
Volumes (Caching)
steps:
  - name: restore-cache
    image: alpine
    commands:
      - ls -l /cache
    volumes:
      - /tmp/cache:/cache  # host:container

  - name: build
    image: golang:1.21
    volumes:
      - /tmp/cache:/cache
    commands:
      - go build -o /cache/app ./...
Matrix Builds

Matrix variables are injected as environment variables. Woodpecker runs a separate build for each combination.

steps:
  test:
    image: golang:${GO_VERSION}
    commands:
      - go test
    matrix:
      GO_VERSION: ["1.19", "1.20", "1.21"]
Labels (Agent Selection)

Select which agent runs your workflow by matching labels:

labels:
  platform: linux/arm64     # only run on arm64 agents
  location: europe          # custom label — agent must have this set

steps:
  - name: build
    image: golang
    commands: go build

Default agent labels: platform={os}/{arch}, hostname={name}, backend=docker, repo=*. Labels with empty values are ignored.


Environment Variables

Woodpecker injects built-in variables at runtime. These can't be overwritten by environment: blocks.

Most-Used Variables
VariableDescriptionExample
CI_REPOowner/namejohn-doe/my-repo
CI_REPO_OWNERrepo ownerjohn-doe
CI_REPO_NAMErepo namemy-repo
CI_REPO_DEFAULT_BRANCHdefault branchmain
CI_COMMIT_SHAfull commit SHAeba09b46...
CI_COMMIT_BRANCHbranch namemain
CI_COMMIT_TAGtag name (empty if not tag event)v1.10.3
CI_COMMIT_MESSAGEcommit messageInitial commit
CI_COMMIT_AUTHORauthor usernamejohn-doe
CI_COMMIT_PULL_REQUESTPR number (PR events only)42
CI_COMMIT_PULL_REQUEST_LABELSPR labels (PR events only)bug,server
CI_PIPELINE_EVENTevent typepush, pull_request, tag, release, cron, manual, deployment
CI_PIPELINE_NUMBERpipeline number8
CI_PIPELINE_URLlink to pipeline in CI UIhttps://ci.example.com/repos/7/pipeline/8
CI_PIPELINE_FILESchanged files (push/PR events, max 500)[".woodpecker.yml","README.md"]
CI_WORKFLOW_NAMEworkflow name (filename without ext)build
CI_STEP_NAMEcurrent step namebuild package
CI_FORGE_TYPEforge namegithub, gitea, gitlab, forgejo, bitbucket
CI_SYSTEM_VERSIONWoodpecker server version2.7.0
Previous Pipeline Variables

Useful for notification logic and comparing runs:

VariableDescription
CI_PREV_PIPELINE_STATUSsuccess or failure
CI_PREV_COMMIT_SHAprevious commit SHA
CI_PREV_PIPELINE_NUMBERprevious pipeline number
String Substitution

Use ${VAR} in settings:, commands:, and when: blocks:

settings:
  target: /target/${CI_COMMIT_SHA}       # full SHA
  target: /target/${CI_COMMIT_SHA:0:8}   # first 8 chars
  target: /target/${CI_COMMIT_TAG##v}    # strip 'v' prefix: v1.0.0 → 1.0.0

Bash-like string operations: ${param,} (lowercase first), ${param^^} (uppercase), ${param:pos:len} (substring), ${param##prefix} (strip prefix), ${param%%suffix} (strip suffix), ${param/old/new} (replace), ${param=default} (default value).

Important: In commands:, escape dollar signs to prevent pre-processing: use $${PATH} not ${PATH} when you want the shell to handle the variable at runtime.

Environment Gotchas
  • environment: cannot expand variables — use commands: with export instead
  • Set global env vars via the Woodpecker server setting WOODPECKER_ENVIRONMENT=KEY:val,KEY2:val2

Handling Missing Features

No Woodpecker plugin for a GitHub Action — Rewrite the logic in commands: using standard Linux tools inside an appropriate image (e.g., alpine, python:3, node:18). For example, instead of actions/setup-node, just use image: node:18.

Need GitHub context variables — Use the CI_* env vars listed above. The equivalent of github.event_name is CI_PIPELINE_EVENT, github.ref is CI_COMMIT_REF, etc.

Skip a build from a commit — Add [SKIP CI] or [CI SKIP] to the commit message (case-insensitive).

Drone CI tricks apply — Woodpecker is a fork of Drone, so Drone StackOverflow answers often work directly.


Research Resources

When you hit an edge case not covered here, consult:

When no plugin exists for a needed action, don't force it — write clear commands: steps with standard images. That's often cleaner than hunting for a plugin that only partially fits.

Comments

Markdown supported