[{"data":1,"prerenderedAt":180},["ShallowReactive",2],{"seo-verification":3,"blog-replace-github-actions-with-woodpecker-ci-on-a-vps-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-replace-github-actions-with-woodpecker-ci-on-a-vps-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"publishedAt":20,"category":21,"categories":26,"featuredImage":28,"bgImage":29,"posterImage":30,"relatedSolution":28,"intro":31,"sections":32,"ctaTitle":107,"ctaBody":108,"ctaButton":109,"ctaUrl":110,"relatedPosts":111},338,"replace-github-actions-with-woodpecker-ci-on-a-vps",{"fr":12,"en":10,"ar":13,"es":14},"remplacer-github-actions-woodpecker-ci-self-hosted-vps","استبدال-github-actions-بـwoodpecker-ci-على-vps","reemplazar-github-actions-con-woodpecker-ci-en-vps","Replace GitHub Actions with Woodpecker CI on a VPS","GitHub announced then suspended billing for self-hosted runners on private repos. Switch to Woodpecker CI: clean syntax, under 50 MB RAM, fixed cost on your VPS.",8,1,false,"2026-09-07T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":24},2,"Automation","automatisation","bg-brand-action\u002F10 text-brand-action",[27],{"id":22,"name":23,"slug":24,"color":25,"icon":24},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fremplacer-github-actions-woodpecker-ci-self-hosted-vps-poster.svg","In December 2025, GitHub announced per-minute billing for self-hosted runners on private repositories (community discussion #182089, 72 upvotes). Faced with a massive community backlash, the pricing was suspended within 48 hours — and as of September 1, 2026, self-hosted runners remain free. The threat is real, however: nothing prevents GitHub from re-activating it. Migrating to Woodpecker CI means protecting yourself against the next announcement — and gaining full sovereignty over your CI\u002FCD in the process. Woodpecker CI is an open source alternative that runs in under 50 MB of RAM, connects to Gitea or Forgejo via OAuth2, and costs nothing beyond your VPS.",[33,37,69,72,94,97,101,104],{"type":34,"title":35,"body":36},"h2","GitHub and runner pricing: what happened in 2025-2026","Until 2025, private repositories included a monthly quota of free minutes. On December 16, 2025, GitHub announced per-minute pricing for runner usage — including **self-hosted runners** running on your own machines. GitHub Discussion #182089 (opened that same month, 72 upvotes) captured the community's concern: teams would be paying GitHub to orchestrate a job running on their own infrastructure, at $0.002 per minute.\n\nFaced with a massive community backlash, GitHub suspended the pricing within 48 hours. As of September 1, 2026, self-hosted runners on private repositories remain free — the measure was never enforced. But the announcement exposed a structural dependency: GitHub can change its terms at any time. Moving the entire CI\u002FCD chain to a self-hosted forge is how you remove that uncertainty. Woodpecker CI is built precisely for this: a lightweight, open source (Apache 2.0) pipeline engine that installs alongside Gitea or Forgejo and has no minute counter.",{"type":38,"title":39,"headers":40,"rows":44},"comparison","GitHub Actions runners vs self-hosted Woodpecker CI",[41,42,43],"Criterion","GitHub Actions (cloud runners)","Woodpecker CI on VPS",[45,49,53,57,61,65],[46,47,48],"Cost","$0.008 \u002F min (Linux) + $0.002 \u002F min orchestration on private repos","Included in the fixed VPS cost (from `{{vps.start.price}}`)",[50,51,52],"CI engine RAM","GitHub-hosted runners: shared resources, not under your control","Woodpecker server + agent: under 50 MB RAM combined",[54,55,56],"Sovereignty","Code and secrets transit through GitHub\u002FMicrosoft infrastructure","Everything stays on your VPS — no external transit",[58,59,60],"Workflow syntax","`.github\u002Fworkflows\u002F*.yml` — widely adopted standard","`.woodpecker.yml` — clean, shorter syntax, not compatible with GitHub Actions",[62,63,64],"Forge requirement","None (GitHub hosts the repository)","Self-hosted Gitea or Forgejo (or GitHub as remote forge)",[66,67,68],"Licence","Proprietary","Apache 2.0",{"type":34,"title":70,"body":71},"Prerequisites before installation","To deploy Woodpecker CI comfortably, check the following requirements.\n\n**VPS**: 1 vCPU and 1 GB of RAM are sufficient for the Woodpecker server, one agent and a lightweight Gitea or Forgejo forge. Plan for 2 GB if you run several builds in parallel or if your jobs build Docker images.\n\n**Software**: Docker and Docker Compose must be installed. If you are starting from a bare VPS, the guide \u003Ca href=\"\u002Fblog\u002Fdemarrer-avec-docker-vps\">Getting started with Docker on a VPS\u003C\u002Fa> covers this step.\n\n**Git forge**: Woodpecker CI relies on OAuth2 for authentication. You need a Gitea or Forgejo instance already running and accessible via HTTPS (for example at `git.your-domain.com`). The guides \u003Ca href=\"\u002Fblog\u002Fheberger-gitea\">Hosting Gitea\u003C\u002Fa> and \u003Ca href=\"\u002Fblog\u002Fheberger-forgejo\">Hosting Forgejo\u003C\u002Fa> detail this setup.\n\n**Domain**: prepare a dedicated subdomain for Woodpecker — for example `ci.your-domain.com` — pointing to your VPS IP, with port 443 open. This subdomain serves as `WOODPECKER_HOST` and as the OAuth2 redirect URL.",{"type":73,"title":74,"steps":75},"steps","Install Woodpecker CI with Docker Compose",[76,79,82,85,88,91],{"title":77,"body":78},"Create an OAuth2 application in Gitea","In Gitea, go to **Settings → Applications → Manage OAuth2 Applications**. Give the application a name (for example `woodpecker`) and enter the redirect URL: `https:\u002F\u002Fci.your-domain.com\u002Fauthorize`. Note the generated **Client ID** and **Client Secret** — you will need them in the next step.\n\nIf you use Forgejo, the procedure is identical: **Settings → Applications → OAuth2**.",{"title":80,"body":81},"Create the docker-compose.yml file","Create the `\u002Fopt\u002Fwoodpecker` directory and place a `docker-compose.yml` file with the two services — server and agent:\n\n```yaml\nservices:\n  woodpecker-server:\n    image: woodpeckerci\u002Fwoodpecker-server:v3\n    restart: always\n    ports:\n      - \"8000:8000\"\n    volumes:\n      - woodpecker-server-data:\u002Fvar\u002Flib\u002Fwoodpecker\u002F\n    environment:\n      - WOODPECKER_OPEN=false\n      - WOODPECKER_HOST=https:\u002F\u002Fci.your-domain.com\n      - WOODPECKER_GITEA=true\n      - WOODPECKER_GITEA_URL=https:\u002F\u002Fgit.your-domain.com\n      - WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}\n      - WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}\n      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}\n\n  woodpecker-agent:\n    image: woodpeckerci\u002Fwoodpecker-agent:v3\n    restart: always\n    command: agent\n    depends_on:\n      - woodpecker-server\n    volumes:\n      - woodpecker-agent-config:\u002Fetc\u002Fwoodpecker\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n    environment:\n      - WOODPECKER_SERVER=woodpecker-server:9000\n      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}\n\nvolumes:\n  woodpecker-server-data:\n  woodpecker-agent-config:\n```\n\nThe server listens internally on port **8000** (web interface) and **9000** (gRPC, agent communication). Do not expose these ports directly: a reverse proxy handles TLS.",{"title":83,"body":84},"Create the .env file","In `\u002Fopt\u002Fwoodpecker`, create a `.env` file containing the three sensitive variables:\n\n```\nWOODPECKER_GITEA_CLIENT=\u003Coauth2-client-id>\nWOODPECKER_GITEA_SECRET=\u003Coauth2-client-secret>\nWOODPECKER_AGENT_SECRET=\u003Clong-random-string>\n```\n\nGenerate `WOODPECKER_AGENT_SECRET` with `openssl rand -hex 32`. This string authenticates the agent with the server — do not share it.",{"title":86,"body":87},"Configure the nginx reverse proxy","Place Woodpecker behind nginx with a Let's Encrypt certificate. Example `server` block for `ci.your-domain.com`:\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name ci.your-domain.com;\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fci.your-domain.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fci.your-domain.com\u002Fprivkey.pem;\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-Proto https;\n    }\n}\n```\n\nObtain the certificate with `certbot certonly --nginx -d ci.your-domain.com` then reload nginx with `systemctl reload nginx`.",{"title":89,"body":90},"Start the stack","From `\u002Fopt\u002Fwoodpecker`, run:\n\n```bash\ndocker compose up -d\n```\n\nWatch the server logs until you see the ready line:\n\n```bash\ndocker compose logs -f woodpecker-server\n```\n\nOpen `https:\u002F\u002Fci.your-domain.com` in your browser. The login page shows a **Sign in via Gitea** button. Click it: Gitea asks for OAuth2 authorisation, then redirects you to the Woodpecker dashboard.",{"title":92,"body":93},"Activate a repository and trigger the first build","In the Woodpecker dashboard, click **+ Add repository**. Your list of Gitea repositories appears. Select a project and activate it. Woodpecker automatically registers a webhook in Gitea to trigger builds on each push.\n\nPush a first commit to this repository: if the `.woodpecker.yml` file is present at the root, the pipeline triggers immediately in the interface.",{"type":34,"title":95,"body":96},"Migrating a GitHub Actions workflow to .woodpecker.yml","Woodpecker CI has its own YAML syntax — it is not compatible with GitHub Actions. The good news: it is shorter. Here is the migration of a classic Node.js workflow.\n\n**Before — `.github\u002Fworkflows\u002Fci.yml`:**\n\n```yaml\nname: CI\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: '20'\n      - run: npm ci\n      - run: npm test\n```\n\n**After — `.woodpecker.yml`:**\n\n```yaml\nsteps:\n  - name: test\n    image: node:20\n    commands:\n      - npm ci\n      - npm test\n```\n\nKey differences: Woodpecker uses a Docker image directly as the runtime (no `setup-node` needed), and each step is a container service. The `checkout` action is implicit — Woodpecker automatically clones the repository into the shared workspace between steps.",{"type":98,"title":99,"body":100},"tip","Pipeline secrets and the WOODPECKER_SECRET variable","Never put tokens, SSH keys or passwords in your versioned `.woodpecker.yml`. Woodpecker manages secrets at the repository or organisation level: in the dashboard, open the repository → **Settings → Secrets → Add secret**. Each secret is injected as an environment variable at build time.\n\nTo use a secret in the pipeline:\n\n```yaml\nsteps:\n  - name: deploy\n    image: alpine\n    environment:\n      SSH_DEPLOY_KEY:\n        from_secret: ssh_deploy_key\n    commands:\n      - echo \"$SSH_DEPLOY_KEY\" > \u002Ftmp\u002Fid_rsa\n      - chmod 600 \u002Ftmp\u002Fid_rsa\n      - ssh -i \u002Ftmp\u002Fid_rsa deploy@your-domain.com .\u002Fdeploy.sh\n```\n\nThe `from_secret` syntax extracts the value from the Woodpecker vault without writing it to logs or to the YAML file.",{"type":34,"title":102,"body":103},"Common troubleshooting","**The agent does not appear in the dashboard.** Check that `WOODPECKER_AGENT_SECRET` is identical in both services of `docker-compose.yml`. If you modified the `.env` after the first start, restart with `docker compose down && docker compose up -d`.\n\n**OAuth2 login fails.** The redirect URL registered in Gitea must match exactly `https:\u002F\u002Fci.your-domain.com\u002Fauthorize`. A trailing slash, a typo or HTTP instead of HTTPS are enough to block the OAuth2 flow. Also check that `WOODPECKER_GITEA_URL` in `docker-compose.yml` points to the **public** URL of your Gitea — the Woodpecker container must be able to resolve it from inside the Docker network.\n\n**The webhook does not trigger.** Open the repository in Gitea → **Settings → Webhooks** and check that the Woodpecker webhook is present and that recent deliveries have no 4xx error. If the webhook URL points to `http:\u002F\u002F` instead of `https:\u002F\u002F`, Gitea may refuse to call it. Reconfigure `WOODPECKER_HOST` with the full HTTPS URL and reactivate the repository in Woodpecker to force the webhook update.\n\n**Builds fail due to out of memory.** The Woodpecker server and agent use under 50 MB at idle — but each build step launches an additional Docker container. On a 1 GB VPS, limit concurrent builds by adding `WOODPECKER_MAX_PROCS=2` to the `woodpecker-agent` service environment.",{"type":34,"title":105,"body":106},"Going further","Once the base pipeline is working, several optimisations are worth exploring.\n\n**Multi-runner.** Add a second `woodpecker-agent` service to your `docker-compose.yml` (same image, same `WOODPECKER_AGENT_SECRET`) to parallelise builds. Each agent is independent and can run on a separate VPS if your workload requires it.\n\n**Docker cache between builds.** Mount a Docker volume in the agent and enable Docker layer caching to avoid re-downloading images on each build. Woodpecker supports native pipeline caching via the `woodpecker-ci\u002Fcache` plugin — add it as a dedicated step in your `.woodpecker.yml`.\n\n**Notifications.** Woodpecker can notify a messaging channel on each build. Community plugins cover Matrix, Slack, Telegram and Mattermost — look for `woodpecker-ci\u002Fnotify-matrix` or `appleboy\u002Fdrone-telegram` in the official documentation.\n\n**Conditional pipelines.** Woodpecker syntax lets you restrict a step to a specific branch or event:\n\n```yaml\nsteps:\n  - name: deploy\n    image: alpine\n    when:\n      branch: main\n      event: push\n    commands:\n      - .\u002Fdeploy.sh\n```\n\nTo go deeper into pipeline setup, the guide \u003Ca href=\"\u002Fblog\u002Fwoodpecker-ci-pipeline-vps-forgejo\">Woodpecker CI and Forgejo: CI\u002FCD pipeline on a VPS\u003C\u002Fa> details advanced steps including Forgejo integration and multi-stage pipelines. The guide \u003Ca href=\"\u002Fblog\u002Fdocker-compose-production-checklist\">Docker Compose production checklist\u003C\u002Fa> covers best practices for securing your Docker stacks in production.","A ServOrbit VPS for your CI\u002FCD pipeline","The `{{vps.start.name}}` plan at `{{vps.start.price}}` — 1 vCPU, 1 GB RAM, fixed IP — is enough to run Woodpecker CI, one agent and a Gitea forge in parallel, with no minute counter.","View Cloud VPS plans","\u002Fvps-cloud",[112,127,145,165],{"id":113,"slug":114,"slugs":115,"title":119,"excerpt":120,"readTime":121,"views":18,"isPinned":19,"publishedAt":122,"category":123,"categories":124,"featuredImage":28,"bgImage":29,"posterImage":126,"relatedSolution":28},212,"woodpecker-ci-and-forgejo-cicd-pipeline-on-a-vps",{"fr":116,"en":114,"ar":117,"es":118},"woodpecker-ci-pipeline-vps-forgejo","woodpecker-ci-وforgejo-خط-أنابيب-cicd-على-خادم-vps","woodpecker-ci-forgejo-pipeline-cicd-vps","Woodpecker CI and Forgejo: CI\u002FCD pipeline on a VPS","Deploy Woodpecker CI v3 with Forgejo on your VPS: Docker Compose setup, YAML pipelines, secrets, multi-arch runners and OAuth troubleshooting.",11,"2026-08-02T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":24},[125],{"id":22,"name":23,"slug":24,"color":25,"icon":24},"\u002Fblog\u002Fcovers\u002Fwoodpecker-ci-pipeline-vps-forgejo-poster.svg",{"id":128,"slug":129,"slugs":130,"title":134,"excerpt":135,"readTime":121,"views":136,"isPinned":19,"publishedAt":137,"category":138,"categories":139,"featuredImage":28,"bgImage":29,"posterImage":141,"relatedSolution":142},273,"migrate-cicd-to-gitea-or-forgejo-leave-github-actions",{"fr":131,"en":129,"ar":132,"es":133},"github-actions-payant-migrer-ci-cd-gitea-forgejo-vps","gitea-وforgejo-بديل-github-actions-المدفوع-على-vps","migrar-cicd-a-gitea-o-forgejo-sin-github-actions","Migrate CI\u002FCD to Gitea or Forgejo: leave GitHub Actions","Since March 2026, GitHub charges CI minutes on private repositories. Migrate to Gitea Actions or Forgejo on a VPS: same YAML files, fixed monthly cost.",0,"2026-08-16T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":24},[140],{"id":22,"name":23,"slug":24,"color":25,"icon":24},"\u002Fblog\u002Fcovers\u002Fgithub-actions-payant-migrer-ci-cd-gitea-forgejo-vps-poster.svg",{"categorySlug":143,"appSlug":144},"development","gitea",{"id":146,"slug":147,"slugs":148,"title":152,"excerpt":153,"readTime":17,"views":136,"isPinned":19,"publishedAt":154,"category":155,"categories":161,"featuredImage":28,"bgImage":29,"posterImage":163,"relatedSolution":164},74,"host-gitea-on-your-own-vps",{"fr":149,"en":147,"ar":150,"es":151},"heberger-gitea","استضافة-gitea-على-خادم-vps-الخاص-بك","alojar-gitea-en-un-vps","Host Gitea on your own VPS in 2026","Deploy Gitea on a VPS with Docker and SSL. Full guide: installation, migration to Forgejo v16 and governance comparison.","2026-04-07T00:00:00+00:00",{"id":156,"name":157,"slug":158,"color":159,"icon":160},7,"Self-hosting","self-hosting","bg-indigo-500\u002F10 text-indigo-400","cloud",[162],{"id":156,"name":157,"slug":158,"color":159,"icon":160},"\u002Fblog\u002Fcovers\u002Fheberger-gitea-poster.svg",{"categorySlug":143,"appSlug":144},{"id":166,"slug":167,"slugs":168,"title":172,"excerpt":173,"readTime":174,"views":22,"isPinned":19,"publishedAt":175,"category":176,"categories":177,"featuredImage":28,"bgImage":29,"posterImage":179,"relatedSolution":28},75,"host-forgejo-on-your-own-vps",{"fr":169,"en":167,"ar":170,"es":171},"heberger-forgejo","استضافة-forgejo-على-خادم-vps-الخاص-بك","alojar-forgejo-en-un-vps","Self-Host Forgejo on Your VPS: Complete 2025 Guide","Deploy Forgejo on your VPS with Docker, SSL, Actions runners and hardened security. Step-by-step guide for a sovereign Git forge.",9,"2026-04-06T00:00:00+00:00",{"id":156,"name":157,"slug":158,"color":159,"icon":160},[178],{"id":156,"name":157,"slug":158,"color":159,"icon":160},"\u002Fblog\u002Fcovers\u002Fheberger-forgejo-poster.svg",1789046178203]