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.yamlfile 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/arm64key 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
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 tohttps://ci.your-domain.com/authorize. Note the Client ID and Client Secret generated; they will be used in the Woodpecker server environment variables.Create the directory and docker-compose.yml
Create the
/opt/woodpecker/directory and place adocker-compose.ymlfile in it. Thewoodpecker-serverservice uses the imagewoodpeckerci/woodpecker-server:v3and exposes ports 8000 (UI) and 9000 (gRPC). Set the variablesWOODPECKER_FORGEJO=true,WOODPECKER_FORGEJO_URL(Forgejo public URL),WOODPECKER_FORGEJO_CLIENT,WOODPECKER_FORGEJO_SECRET,WOODPECKER_AGENT_SECRETandWOODPECKER_HOST=https://ci.your-domain.com. Thewoodpecker-agentservice useswoodpeckerci/woodpecker-agent:v3, mounts/var/run/docker.sock, and receivesWOODPECKER_SERVER=woodpecker-server:9000plus the sameWOODPECKER_AGENT_SECRET.Configure the HTTPS reverse proxy
Configure Traefik or Caddy to terminate TLS on
ci.your-domain.comand proxy towoodpecker-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.Start the stack
In
/opt/woodpecker, rundocker compose up -d. Check logs withdocker compose logs -f woodpecker-serveruntil you seeserver started. The agent appears in the server logs with anagent connectedmessage; if it does not appear within thirty seconds, check thatWOODPECKER_AGENT_SECRETis identical in both services.Log in and activate the first repository
Open
https://ci.your-domain.comand 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.Add the .woodpecker.yaml file to the repository
At the root of the repository, create
.woodpecker.yaml. A minimal example with three steps:lint(imagenode:20, commandnpm run lint),test(imagenode:20, commandnpm test) anddeployconditional on themainbranch 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.Verify data persistence
By default, Woodpecker stores its SQLite database inside the container. For a durable installation, mount a named volume on
/var/lib/woodpeckerin 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.