Tutorial

Woodpecker CI and Forgejo: CI/CD pipeline on a VPS

Automation11 min read8 steps

Woodpecker CI is an open source pipeline engine — a community fork of Drone CI under the Apache 2.0 license — designed to pair natively with Forgejo. As of version 3.18 (August 2026), each step runs in an isolated Docker container, and the server + agent pair consumes less than 50 MB of RAM. On a VPS, builds run as close as possible to the git repository: no cloud queue, no minute quota to watch, and no integration secret leaves your network.

Contents· Why move from cloud CI to a pipeline on your VPS1/11
  1. 01Why move from cloud CI to a pipeline on your VPS
  2. 02What Woodpecker CI brings to a VPS setup
  3. 03Measured prerequisites: VPS, Forgejo and networking
  4. 04Woodpecker CI vs Forgejo Actions: when to use which
  5. 05Deploy Woodpecker CI on your VPS
  6. 06Writing your first .woodpecker.yaml pipeline
  7. 07Secrets and environment variables in pipelines
  8. 08Multi-architecture runners: x86_64 and ARM64
  9. 09Troubleshooting: five common errors
  10. 10Integrating with the ServOrbit marketplace: automatic deployment on a VPS
  11. 11From git forge to continuous deployment: a complete sovereign stack

Why move from cloud CI to a pipeline on your VPS

Cloud CI services charge per build minute and impose shared queues whose wait time grows with volume. As repositories grow or tests multiply, the bill quickly exceeds the cost of a dedicated VPS. Hosting Forgejo and Woodpecker CI on the same server eliminates this network latency: the runner reads the code locally, without routing through third-party infrastructure. You also control the update cycle — no pricing change or log retention policy applies without your consent. For teams with regulatory or confidentiality constraints, this is a decisive advantage: integration secrets (SSH deployment keys, registry access tokens) never cross your network perimeter. Finally, the Apache 2.0 license ensures the source code remains auditable and the project cannot be locked behind an exclusive commercial offering.

What Woodpecker CI brings to a VPS setup

  • Docker isolation per step — each step runs in its own container with no shared state between jobs; a failing step does not corrupt subsequent ones.
  • Minimal memory footprint — server + agent start in under 50 MB of RAM combined, leaving most resources for the builds themselves.
  • Minimal YAML syntax — a .woodpecker.yaml file at the root of the repository is enough to describe the entire pipeline; the syntax is cleaner than GitHub Actions.
  • Native Forgejo integration — Forgejo's OAuth2 is the only authentication mechanism required; webhooks are registered automatically.
  • Multi-architecture without extra plugins — the platform: linux/arm64 key routes a job to an ARM64 agent; no additional abstraction needed.
  • Centralized secrets per repository or global — sensitive variables are injected at build time and never appear in the versioned YAML file.
  • Apache 2.0 license — auditable source code, active fork community, no dependency on a proprietary vendor.

Measured prerequisites: VPS, Forgejo and networking

Woodpecker CI can be installed on the same VPS as Forgejo or on a dedicated server. For both services combined, plan for at least 1 GB of RAM and 2 GB for comfortable usage with several active repositories in parallel. On the software side: Docker Engine 24 or later, Docker Compose v2 (the docker compose command, not docker-compose), a dedicated subdomain for Woodpecker — for example ci.your-domain.com — pointing to the VPS IP, port 443 open for HTTPS, and SSH access. Forgejo must be reachable from the Woodpecker container: either via the internal Docker network if both services share the same host, or via its public HTTPS URL if Woodpecker is on a separate VPS. Woodpecker CI v3 is compatible with Forgejo v1.20 and above (the version that introduced Forgejo Actions); older Forgejo versions use the Gitea endpoint, which remains supported.

Woodpecker CI vs Forgejo Actions: when to use which

Forgejo Actions — available since Forgejo v1.19 in experimental mode, progressively stabilized — replicates the GitHub Actions syntax: if your teams already have .github/workflows/ pipelines in production, migration is nearly transparent. Forgejo Actions is the natural choice for a repository fleet that coexists with GitHub or reuses public marketplace actions. Woodpecker CI meets different needs: its YAML syntax is more direct, less burdened with concepts inherited from GitHub, and its server-agent architecture allows decoupling the git forge from the CI engine — useful when multiple forges (Forgejo, Gitea, GitLab) need to share the same runner pool. Woodpecker is also better suited to multi-architecture build scenarios, where you want to explicitly route a job to an ARM or x86 agent. In summary: choose Forgejo Actions if GitHub Actions compatibility is the priority, and Woodpecker CI if you prefer a lean syntax, a decoupled deployment, or a heterogeneous runner pool.

Deploy Woodpecker CI on your VPS

  1. Create an OAuth App in Forgejo

    In Forgejo, go to Settings → Applications → Manage OAuth2 Applications. Give the application a name (for example woodpecker) and set the redirect URL to https://ci.your-domain.com/authorize. Note the Client ID and Client Secret generated; they will be used in the Woodpecker server environment variables.

  2. Generate a shared server-agent secret

    Generate a strong random string: openssl rand -hex 32. This value will be set as WOODPECKER_AGENT_SECRET in both the server and agent services; it authenticates gRPC communication between the two. Store it in a secrets manager or an unversioned .env file.

  3. Create the directory and docker-compose.yml

    Create the /opt/woodpecker/ directory and place a docker-compose.yml file in it. The woodpecker-server service uses the image woodpeckerci/woodpecker-server:v3 and exposes ports 8000 (UI) and 9000 (gRPC). Set the variables WOODPECKER_FORGEJO=true, WOODPECKER_FORGEJO_URL (Forgejo public URL), WOODPECKER_FORGEJO_CLIENT, WOODPECKER_FORGEJO_SECRET, WOODPECKER_AGENT_SECRET and WOODPECKER_HOST=https://ci.your-domain.com. The woodpecker-agent service uses woodpeckerci/woodpecker-agent:v3, mounts /var/run/docker.sock, and receives WOODPECKER_SERVER=woodpecker-server:9000 plus the same WOODPECKER_AGENT_SECRET.

  4. Configure the HTTPS reverse proxy

    Configure Traefik or Caddy to terminate TLS on ci.your-domain.com and proxy to woodpecker-server:8000. With Caddy, a minimal block is sufficient: ci.your-domain.com { reverse_proxy woodpecker-server:8000 }. Never expose port 8000 directly on the public IP; port 9000 (gRPC) must remain accessible only on the internal Docker network.

  5. Start the stack

    In /opt/woodpecker, run docker compose up -d. Check logs with docker compose logs -f woodpecker-server until you see server started. The agent appears in the server logs with an agent connected message; if it does not appear within thirty seconds, check that WOODPECKER_AGENT_SECRET is identical in both services.

  6. Log in and activate the first repository

    Open https://ci.your-domain.com and log in with your Forgejo account via OAuth2. In the dashboard, click "Add repository", then select the project to activate. Woodpecker automatically registers a webhook in Forgejo to trigger builds on each push or pull request.

  7. Add the .woodpecker.yaml file to the repository

    At the root of the repository, create .woodpecker.yaml. A minimal example with three steps: lint (image node:20, command npm run lint), test (image node:20, command npm test) and deploy conditional on the main branch that runs a remote SSH script. Push the file: a pipeline triggers immediately in Woodpecker and the result appears in the interface and in the commit status in Forgejo.

  8. Verify data persistence

    By default, Woodpecker stores its SQLite database inside the container. For a durable installation, mount a named volume on /var/lib/woodpecker in the server service and back up this volume regularly. A restart or image update without a named volume erases build history and the configuration of activated repositories.

Writing your first .woodpecker.yaml pipeline

A .woodpecker.yaml file describes a sequence of steps executed sequentially in separate Docker containers. Each step has a name, an image, and a list of commands. Steps can share the workspace (the cloned directory) via a volume mounted automatically by Woodpecker. For a Node.js project, a basic pipeline includes a lint step (node:20, npm ci && npm run lint), a test step (node:20, npm test) and a build step (node:20, npm run build). For a Docker project, add a step that uses the official woodpeckerci/plugin-docker-buildx plugin to build and push the image to a registry. Conditional triggering is expressed with the when block: when: { branch: main, event: push } limits the deploy step to the main branch on direct pushes, excluding pull requests.

Secrets and environment variables in pipelines

Woodpecker distinguishes two levels of secrets: repository secrets (visible only in pipelines of the relevant repository) and global secrets (accessible to all repositories, to be created with care). A secret is declared in the dashboard — in the repository's Secrets section — then referenced in the pipeline with the from_secret syntax. For example, a deployment SSH key named deploy_key is injected into a step via environment: { SSH_KEY: { from_secret: deploy_key } }. Secrets are never passed to pull requests from external forks by default, preventing exfiltration by a malicious contributor. For non-sensitive variables that need to be shared across multiple repositories, use the WOODPECKER_ENVIRONMENT variable at the server level: KEY=value pairs defined there are available in all pipelines without declaration in the YAML.

Place the Woodpecker server behind Traefik or Caddy with HTTPS and never expose it directly on port 8000. Enable allowlist authentication (WOODPECKER_ADMIN) to restrict dashboard access to authorized Forgejo accounts only. Store all pipeline secrets (SSH tokens, API keys, Docker registry passwords) in the repository secrets of the Woodpecker dashboard — they are injected as environment variables during the build without appearing in the versioned .woodpecker.yaml. Finally, mount a named Docker volume to persist the Woodpecker SQLite database: a restart without a volume erases build history.

Multi-architecture runners: x86_64 and ARM64

Woodpecker CI v3 natively supports multi-architecture agents: each agent announces its platform to the server (linux/amd64, linux/arm64, linux/arm/v7) and the server routes jobs to the matching agent. To declare an ARM64 job, add platform: linux/arm64 at the pipeline level in .woodpecker.yaml. If you have an ARM VPS (Ampere, Raspberry Pi 4, or Hetzner ARM server) and an x86_64 VPS, deploy an agent on each with the same WOODPECKER_AGENT_SECRET and let the server distribute builds. This mechanism is useful for cross-compiling binaries, testing Docker image compatibility across architectures, or validating a system package. The woodpeckerci/plugin-docker-buildx plugin goes further and uses QEMU to produce multi-arch images from a single agent, but native builds on the target architecture are always faster.

Troubleshooting: five common errors

Five issues come up repeatedly during Woodpecker CI installation or use. First: the agent does not connect to the server (agent could not auth). Most frequent cause: WOODPECKER_AGENT_SECRET is not identical in both services. Check variables with docker compose exec woodpecker-server env | grep AGENT_SECRET. Second: OAuth2 login fails with Error while authenticating against OAuth provider. Likely cause: the redirect URL in the Forgejo OAuth2 application does not exactly match WOODPECKER_HOST. Check for a trailing slash and HTTPS scheme consistency. Third: pipelines stay stuck in pending. The agent may be connected but its platform label matches no pipeline; remove the platform clause if it is not needed. Fourth: secrets are not injected into steps. Secrets are only passed to pipelines triggered on internal branches, never on fork PR events by default. Check that the triggering event is push or tag. Fifth: after upgrading to v3.18, old build logs are missing. This version includes a log storage migration; if the migration is interrupted, restart the server container with write permissions on the data volume so it can resume.

Integrating with the ServOrbit marketplace: automatic deployment on a VPS

ServOrbit offers Woodpecker CI in its marketplace: the application installs on a Cloud VPS with pre-configured Docker and a fixed IP, in a few clicks from the client portal. Once the VPS is provisioned, simply point WOODPECKER_FORGEJO_URL to your existing Forgejo forge and enter the OAuth2 keys for pipelines to start running. This integration is particularly useful for agencies or technical teams that want to isolate the CI engine from the forge server: Forgejo on one VPS, Woodpecker on a second, the two communicating via HTTPS. VPS resources are adjustable at any time from the client portal — add RAM or vCPU without reinstalling — allowing the runner pool to be sized according to actual build load.

From git forge to continuous deployment: a complete sovereign stack

Forgejo manages code, issues and pull request reviews; Woodpecker CI orchestrates test and deployment pipelines. The two communicate via OAuth2 and webhooks on your own network, with no dependency on GitHub, GitLab or a cloud integration service. This self-hosted stack meets the sovereignty requirements of agencies and technical teams that do not want their intellectual property or integration secrets to transit through external platforms. With Woodpecker CI v3 and Forgejo v1.20 or above, you have a complete development environment — git forge, CI/CD, Docker registry if needed — entirely under your control, on one or two VPS, at a predictable monthly cost and without build-minute quotas.

Launch your CI/CD stack on a ServOrbit VPS

A ServOrbit Cloud VPS with preconfigured Docker and a fixed IP lets you deploy Forgejo and Woodpecker CI in minutes, with resources you can scale as your pipelines grow.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab