Your First Woodpecker Pipeline - Anatomy of .woodpecker.yml

Status: Active
Last Updated: 2026-08-26
Category: CI/CD - Phase 3: Woodpecker CI
Prerequisites: woodpecker-installation
Time: 1-2 hours
Tags: woodpecker, pipeline, yaml, docker-buildx, secrets, matrix, debugging, ci

Summary

Every Woodpecker pipeline is a single .woodpecker.yml file living in your repository โ€” conditions (when), steps, container images, commands, and secrets in one versioned YAML file. This guide dissects the syntax, builds a realistic test + Docker-image-publish pipeline, introduces secrets management and matrix builds, and shows how to debug failures from the logs.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Pipeline Basics
  2. Anatomy of .woodpecker.yml
  3. When: Controlling When Pipelines Run
  4. A Realistic Pipeline: Test Then Publish
  5. Building and Pushing Docker Images
  6. Secrets Management
  7. Matrix Builds
  8. Debugging Failed Pipelines
  9. Troubleshooting
  10. Key Takeaways
  11. Next Steps

Pipeline Basics

From the moment you activated a repository (woodpecker-installation), every push, tag, or PR triggers a webhook to Woodpecker. The server then looks for one thing: a pipeline definition at the repo root.

my-app/
โ”œโ”€โ”€ src/
โ”œโ”€โ”€ Dockerfile
โ””โ”€โ”€ .woodpecker.yml     โ† this is all it takes

Two facts that shape everything else:

  1. The clone step is implicit. Woodpecker clones your repo into a shared workspace before your first step runs; every step sees the same files.
  2. Each step runs in its own container, defined by image. Steps share the workspace directory, but not processes or installed packages โ€” anything you need must come from the image, be committed to disk in the workspace, or be reinstalled per step.

Anatomy of .woodpecker.yml

The minimal official example:

when:
  - event: push
    branch: main

steps:
  - name: build
    image: debian
    commands:
      - echo "This is the build step"
      - echo "some-data" > some-file.txt

  - name: a-test-step
    image: alpine
    commands:
      - echo "Testing ..."
      - cat some-file.txt

Piece by piece:

Key Meaning
when (top-level) Conditions for running this workflow at all
steps: Ordered list of work units; run sequentially top-to-bottom
name Identifier shown in the UI and used for depends_on
image Any OCI image from any registry you can pull โ€” debian, node:22-alpine, your own
commands Shell commands executed inside the step's container, in the workspace

You can also put multiple workflows in .woodpecker/ as separate files (e.g. .woodpecker/test.yml, .woodpecker/publish.yml) โ€” handy once pipelines grow.

When: Controlling When Pipelines Run

when accepts a list of condition blocks; the workflow runs if any block matches:

when:
  - event: push
    branch: main          # pushes to main
  - event: tag            # any tag push
  - event: pull_request   # PRs from anyone

Common filters:

when:
  - event: push
    branch:
      include: [main, "release/*"]
    path:                 # only when relevant files changed
      include: ["src/**", ".woodpecker/**"]
  - event: cron
    cron: nightly-audit

Step-level when works the same way and toggles individual steps:

  - name: deploy
    image: alpine
    commands: [...]
    when:
      - event: push
        branch: main

๐Ÿ’ก Without any when, the workflow runs on every event including pull requests โ€” usually not what you want for deploy steps.

A Realistic Pipeline: Test Then Publish

A Node app: install โ†’ lint โ†’ test on every push/PR; build and publish the image only on main.

# .woodpecker.yml
when:
  - event: push
    branch: main
  - event: pull_request

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

  - name: lint
    image: node:22-alpine
    commands:
      - corepack enable && npm run lint

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

  - name: publish
    image: woodpeckerci/plugin-docker-buildx:5
    settings:
      repo: registry.example.com/team/my-app
      tags: "${CI_COMMIT_SHA},latest"
      registry: registry.example.com
      username:
        from_secret: registry_user
      password:
        from_secret: registry_password
    when:
      - event: push
        branch: main

Notes:

Building and Pushing Docker Images

The idiomatic way is the buildx plugin โ€” a plugin is just another container whose behavior is configured via settings. Plugins never run arbitrary shell, which makes them safe to receive secrets.

  - name: publish
    image: woodpeckerci/plugin-docker-buildx:5
    settings:
      # build context & Dockerfile default to the workspace root
      repo: registry.example.com/team/my-app
      tags:
        - latest
        - "${CI_COMMIT_TAG}"       # when building from a tag event
      registry: registry.example.com
      username:
        from_secret: registry_user
        # plugin settings become PLUGIN_* env vars inside the container
      password:
        from_secret: registry_password
      platforms: linux/amd64,linux/arm64

The plugin needs access to a Docker daemon to build. On our agent setup (Docker backend), that's already satisfied through the mounted socket โ€” no extra configuration required for standard builds. Multi-arch via QEMU may need platforms support enabled on the runner.

Prefer a private registry? Create registry credentials once under Repository Settings โ†’ Registries instead of baking them into steps; Woodpecker injects them automatically when pulling private base images.

๐Ÿ“– Need a better Dockerfile first? See containers/dockerfile-guide.

Secrets Management

Woodpecker stores named variables in a central secret store, consumed in YAML with from_secret. Three levels, most-specific wins:

Level Scope Set by
Repository All pipelines of one repo Repo admins
Organization All repos in an org Org owners
Global Entire instance Instance admins only

Create one via UI (Repo โ†’ Settings โ†’ Secrets) or CLI:

woodpecker-cli repo secret add \
  --repository team/my-app \
  --image woodpeckerci/plugin-docker-buildx \
  --event push \
  --event tag \
  --name registry_password \
  --value "$(cat ~/.registry-pass)"

Safety rules baked into the design:

  - name: use-secret-in-shell
    image: alpine
    environment:
      TOKEN_ENV:
        from_secret: secret_token
    commands:
      - echo $${TOKEN_ENV}     # $$ escapes Woodpecker's ${} preprocessing

Matrix Builds

Run the same workflow across combinations of versions/platforms. Woodpecker fans out one full workflow per combination (max 27 axes):

matrix:
  NODE_VERSION:
    - 20
    - 22
  include:
    - NODE_VERSION: 22
      EXPERIMENTAL: "true"

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

${NODE_VERSION} is interpolated before YAML parsing โ€” even into image:. The multi-platform pattern is especially useful for agents on different architectures:

matrix:
  platform:
    - linux/amd64
    - linux/arm64
labels:
  platform: ${platform}
steps:
  - name: test
    image: alpine
    commands:
      - echo "running on ${platform}"

Debugging Failed Pipelines

When a pipeline goes red, the UI gives you a waterfall: each step with duration and exit code. Click the failed step and read the log bottom-up:

+ npm test -- --run
...
FAIL src/api.test.ts
  โ— GET /health returns 200
    expected 200, received 500
exit code 1

Reading rules:

  1. Last lines first โ€” the failing command is almost always the final block before exit code N.
  2. Compare with local execution. You can reproduce without pushing: woodpecker-cli exec .woodpecker.yml runs the workflow locally against a supported backend โ€” same images, same commands.
  3. Check which when matched. A pipeline that "didn't run" is usually a filter mismatch; the server log shows skipped events, or temporarily relax when.
  4. Secret problems look like empty values. If a step prints blank where a secret should be, check the secret's allowed events/images โ€” restrictions silently exclude mismatched steps.
  5. Clone issues are webhook issues. Nothing appears at all? Return to woodpecker-installation and verify webhook deliveries return HTTP 200.

Troubleshooting

Symptom Likely cause / fix
pipeline has no workflows / nothing triggers File must be named .woodpecker.yml (leading dot) at repo root, or valid YAML under .woodpecker/. Check indentation โ€” tabs are invalid YAML.
image: pull access denied Private image without registry credentials โ€” add them under Repo Settings โ†’ Registries.
Step fails with command not found Each step is a fresh container; the tool lives in a different image. Use an image containing the toolchain, or install it in commands first.
Files written in step 1 missing in step 2 Only the workspace persists. Write outputs relative to the workspace, not /tmp or $HOME.
Secret is empty / masked value leaks as literal ${SECRET} Forgot from_secret, wrong secret name, or secret restricted to other events/images. Also remember $${VAR} escaping inside commands.
publish step fails auth to registry from_secret names don't match created secrets, or secret not enabled for push/tag events.
Matrix job count explodes Axes multiply: 3ร—3ร—3 = 27 jobs. Cap combinations with include:.
Works locally, fails in CI Different working directory, missing env vars, or architecture mismatch (arm vs amd). Print env and pwd in a debug step.

Key Takeaways

Next Steps

Sources & Related

Researched via headless-browser session (DuckDuckGo results were bot-challenged; official documentation fetched directly):

Change Log

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse