Testing Woodpecker Pipelines - Lint Locally, Debug on CI
Status: Active Last Updated: 2026-08-26 Category: CI/CD - Woodpecker Prerequisites: woodpecker-first-pipeline, woodpecker-installation Time: 2 hours Tags: woodpecker, ci, pipeline, linting, debugging, cli
Summary
Waiting for a push to find out your .woodpecker.yaml is broken wastes 5โ10 minutes per mistake. This article covers linting pipelines before commit, running steps locally with the woodpecker-cli exec, forcing re-runs without dummy commits, and a systematic method for debugging failures on the server.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Lint and validate pipelines locally before pushing
- โ
Execute pipeline steps locally with
woodpecker-cli exec - โ Use debug-friendly step settings (privileged, failure: ignore, detach)
- โ
Read logs, restart failed pipelines, and use
whenfilters to isolate problems - โ Test tag/PR event behavior without polluting history
Context / Why This Matters
Woodpecker 3.x gives you nearly all of CI's execution model on your laptop. Teams that adopt local validation catch YAML syntax errors, missing images, and bad commands in seconds instead of per-push round trips โ and their commit history stays free of "fix CI" noise.
Implementation / Core Content
Install the CLI
# Linux binary (match your server's major version, 3.x)
curl -LO https://github.com/woodpecker-ci/woodpecker/releases/download/v3.6.0/woodpecker-cli_3.6.0_linux_amd64.tar.gz
tar xzf woodpecker-cli_*_linux_amd64.tar.gz
sudo install -m755 woodpecker-cli /usr/local/bin/
woodpecker-cli --version # expect 3.x
Lint Before You Push
cd my-project
woodpecker-cli lint .woodpecker.yaml
# .woodpecker.yaml: ok
Lint catches: invalid YAML, unknown step fields, missing required image:, duplicate step names, malformed when: conditions. It does not run your commands โ pair it with exec below.
For editor integration, most setups also accept a JSON schema check; a minimal sanity check without the CLI:
python3 -c "import yaml,sys; yaml.safe_load(open('.woodpecker.yaml'))" && echo "yaml ok"
Run Steps Locally with exec
exec runs your real pipeline file through the same container backend locally:
woodpecker-cli exec .woodpecker.yaml
# Single step only:
woodpecker-cli exec --step-name test .woodpecker.yaml
What works out of the box in exec: image, commands, environment, secrets: from a local .env (--env-file), volumes. What doesn't: Forgejo status updates, matrix fan-out from webhooks, plugins that need instance credentials.
Example pipeline and local run:
steps:
test:
image: golang:1.22
commands:
- go vet ./...
- go test -race ./...
$ woodpecker-cli exec .woodpecker.yaml
::group::test
go: downloading ...
ok acme.dev/api/internal/api 0.412s
::endgroup::
Event Simulation Without Dummy Commits
Local files simulate events for filter logic:
# Simulate what a tag push would look like
woodpecker-cli exec --event tag \
--metadata-file ci/metadata.json .woodpecker.yaml
On the server, to test a branch/tag path without new commits:
- Re-run an old pipeline: Woodpecker UI โ Pipeline โ โฎ โ Restart. Restarted pipelines keep their original event/branch context.
- Or push an existing ref again:
git push -f origin <sha>:refs/tags/v0.0-testthen delete it. - Or trigger via API with a token:
curl -s -X POST "https://ci.example.com/api/repos/$REPO_ID/pipelines" \
-H "Authorization: Bearer $WP_TOKEN" \
-d '{"branch":"main"}'
Debugging Techniques Inside Pipelines
Deliberate failure capture:
steps:
flaky-step:
image: alpine:3.20
commands:
- set -x # echo each command with expanded variables
- env | sort # dump environment when hunting var issues
always-cleanup:
image: alpine:3.20
when:
status: [success, failure] # runs even after failures
commands:
- docker system df || true
Useful step-level knobs while iterating:
| Setting | Purpose |
|---|---|
failure: ignore |
Let a diagnostic step run to completion without failing the pipeline |
detach: true |
Start a sidecar (DB, redis) and continue |
backend_options limits |
Reproduce OOM by capping memory deliberately |
Reading Failures on the Server
Pipeline view โ click failed step, not just the red banner. Log reading order:
- Last ~30 lines first โ exit codes and error messages live at the bottom.
- Search the full log for
exit codeandERROR. - Compare against the local
execrun of the same step. - Check the agent logs if the step never started (
docker logs woodpecker-agent) โ image pull failures and volume permission issues appear there, not in step logs.
# Agent-side triage on the runner host
docker logs --tail 100 woodpecker-agent 2>&1 | grep -iE 'error|pull'
docker system prune # reclaim disk if pulls fail from ENOSPC
Isolating Problems with when
Temporarily narrow triggers while bisecting:
when:
event: push
branch: ci-experiments # only run on this scratch branch during debugging
Push to ci-experiments freely; merge back once green. Delete the branch filter before merging.
Practical Examples
Example 1: Local-first workflow
# Pre-commit hook: never push broken YAML again
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
command -v woodpecker-cli >/dev/null || exit 0
for f in .woodpecker*.yaml; do
[ -e "$f" ] || continue
woodpecker-cli lint "$f" || exit 1
done
EOF
chmod +x .git/hooks/pre-push
Example 2: Debug a failing build step end-to-end
# 1. Reproduce locally
woodpecker-cli exec --step-name build .woodpecker.yaml
# โ fails: "npm: not found"
# 2. Fix the image in the pipeline (node:golang mismatch), lint, rerun
sed -i 's/image: golang:1.22/image: node:20/' .woodpecker.yaml
woodpecker-cli lint .woodpecker.yaml && woodpecker-cli exec .woodpecker.yaml
# 3. Commit the fix, verify remotely, watch the run
git add .woodpecker.yaml && git commit -m "ci: use node image for build"
git push
# open https://ci.example.com/acme-corp/api-service and confirm green
Common Pitfalls & Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
lint passes but CI fails |
Differences: secrets, env, network, mounted volumes | Reproduce with exec + --env-file; check agent-side logs |
| Step hangs forever at start | Image pull blocked (registry unreachable) | Check agent logs; pre-pull docker pull <image> on the runner |
| Pipeline skipped entirely | when: didn't match actual event metadata |
Inspect pipeline's "Metadata" tab; compare with filters |
exec can't reach localhost services |
Containers have their own network namespace | Use service names/Docker networks, or host.docker.internal |
| Secrets empty in local exec | No .env supplied |
Run with --env-file .env.secrets |
| Restart still fails identically | Cached workspace from previous attempt | Use "Restart from failure" vs clean restart appropriately; clear agent workspace volume if stale |
| Logs truncated in UI | Large output exceeds log retention | Increase WOODPECKER_LOG_STORE/log config or reduce verbosity |
Next Steps / Ops Actions
- Manage credentials properly before scaling up: ci-secrets-management
- Speed up slow pipelines: caching-strategies
- Automate trigger verification: webhooks
Sources & Related Articles
External references consulted:
- https://woodpecker-ci.org/docs/usage/linter
- https://woodpecker-ci.org/docs/usage/cli
- https://woodpecker-ci.org/docs/usage/workflow-syntax
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation covering linting, local execution, event simulation, and server-side debugging.