Why Woodpecker CI Deserves a Spot in Your Self-Hosted Stack
Continuous integration pipelines have a reputation for being expensive, opaque, or tightly coupled to a vendor’s platform. Woodpecker CI cuts against all of that. It is a lightweight, open-source CI/CD server that runs entirely on your own infrastructure, uses a dead-simple YAML pipeline syntax, and integrates directly with self-hosted Git platforms like Gitea, Forgejo, and GitLab. If you have already moved your code off GitHub and onto your own server, running your pipelines on someone else’s cloud starts to feel like a contradiction.
This guide walks through setting up Woodpecker CI using Docker Compose, connecting it to a Gitea instance, configuring your first pipeline agent, and writing a working pipeline file. The process takes less than an hour and gives you a fully functional CI server you control completely.

What You Need Before Starting
Woodpecker CI runs as two separate services: a server and one or more agents. The server handles the web interface, API, and pipeline scheduling. The agent is what actually executes pipeline steps. Both run as Docker containers, so the primary requirement is a Linux host with Docker and Docker Compose installed. A fresh Ubuntu 22.04 or Debian 12 VPS with at least 1 GB of RAM works fine for a personal or small team setup. You will also need a domain name pointed at your server if you want HTTPS, which Woodpecker requires for OAuth callbacks to function correctly.
On the Git side, you need an existing Gitea or Forgejo instance. Woodpecker authenticates users via OAuth2 through your Git platform, so that connection is not optional – it is how login works. If you are not yet running a self-hosted Git server, getting one set up first is the right order of operations. Once you have both a domain and a running Git platform, you are ready to proceed.
Creating the OAuth Application and Configuring Docker Compose
Log into your Gitea instance as an admin and navigate to Settings, then Applications. Create a new OAuth2 application. Give it a name like “Woodpecker CI” and set the redirect URI to https://ci.yourdomain.com/authorize, replacing the domain with wherever you plan to host Woodpecker. Gitea will generate a client ID and a client secret. Copy both immediately – the secret is only shown once.
On your server, create a directory for the Woodpecker setup and add a docker-compose.yml file. The compose file defines two services. The first is woodpecker-server, using the image woodpeckerci/woodpecker-server:latest. Set the environment variables WOODPECKER_OPEN to false (this prevents random users from registering), WOODPECKER_HOST to your full HTTPS URL, WOODPECKER_GITEA to true, WOODPECKER_GITEA_URL to your Gitea instance URL, and WOODPECKER_GITEA_CLIENT and WOODPECKER_GITEA_SECRET to the values you copied earlier. You also need to set WOODPECKER_AGENT_SECRET to a long random string – this is the shared secret between your server and its agents.
The second service is woodpecker-agent, using woodpeckerci/woodpecker-agent:latest. The agent needs two environment variables: WOODPECKER_SERVER pointing to the server’s internal hostname and gRPC port (woodpecker-server:9000), and WOODPECKER_AGENT_SECRET set to the exact same random string you used for the server. Mount the Docker socket into the agent container at /var/run/docker.sock so it can spin up pipeline step containers. Both services should share a Docker network defined in the compose file.
For persistent storage, add a named volume for the server and mount it to /var/lib/woodpecker. This is where Woodpecker stores its SQLite database by default. SQLite is perfectly adequate for personal use and small teams. If you eventually need something heavier, Woodpecker supports PostgreSQL – just add the WOODPECKER_DATABASE_DRIVER and WOODPECKER_DATABASE_DATASOURCE variables and swap in a database service. For now, SQLite keeps the setup clean.

Putting a Reverse Proxy In Front
Woodpecker’s web UI runs on port 8000, and gRPC traffic for agent communication uses port 9000. Neither should be exposed directly to the internet. A reverse proxy – Caddy or Nginx are the most common choices – handles SSL termination and forwards traffic to the correct ports. With Caddy, the configuration is minimal: a single server block pointing your CI domain at localhost:8000 handles HTTPS automatically via Let’s Encrypt. The gRPC port does not need to be publicly exposed if your agent runs on the same host, which it does in this single-server setup.
Once your proxy is configured and DNS is resolving, run docker compose up -d from your Woodpecker directory. Visit your CI domain in a browser and you should see a Woodpecker login screen. Clicking login redirects you to Gitea for OAuth authorization. After approving access, Woodpecker creates your account automatically. The first user to log in becomes the admin.
Writing Your First Pipeline
Woodpecker pipelines live in a file called .woodpecker.yml at the root of any repository you want to build. The syntax is intentionally minimal. A basic pipeline for a Node.js project looks like this: define a steps block, give each step a name, specify a Docker image, and list the commands to run. Woodpecker clones your repository into each step container automatically, so you start with your code already present in the working directory.
To activate a repository, navigate to it inside the Woodpecker UI after logging in. Click the toggle to enable it. Woodpecker registers a webhook with Gitea automatically, so every push or pull request triggers the pipeline without any manual configuration. You can also control exactly which events fire a pipeline – pushes to main only, pull requests, or tagged releases – using the when block inside each step or at the pipeline level.
Pipeline steps run in sequence by default. To run steps in parallel, group them under a depends_on structure. Secrets – API keys, deployment tokens, passwords – are stored in Woodpecker’s secret store under repository or organization settings and injected into pipeline steps as environment variables. They never appear in logs. If you are already running Vaultwarden as a self-hosted password vault, Woodpecker’s built-in secret store handles the CI-specific credentials separately, keeping pipeline secrets scoped to the repositories that need them.

One detail worth getting right early: Woodpecker agents pull pipeline step images from Docker Hub by default, which means cold pipeline runs on a fresh server can be slow while images download. Pinning step images to specific versions rather than latest tags makes pipelines reproducible and speeds up subsequent runs once the image cache is warm. For projects with large dependency trees, adding a cache volume mount to your agent container – pointed at a directory on the host – can cut build times significantly on repeated runs.





