Back to articles
Technology Insight

Building an Isolated GitOps CI/CD Pipeline for Untrusted Code Using Woodpecker CI and gVisor on a VPS

May 30, 2026

Introduction: The Security Blind Spot in Modern CI/CD

In the era of DevOps and GitOps, automated continuous integration and continuous delivery (CI/CD) pipelines are the backbone of software development. However, running automated pipelines introduces significant security vulnerabilities, particularly when dealing with untrusted code, open-source pull requests, or multi-tenant environments. Standard containerized CI/CD runners share the host operating system's kernel, meaning a malicious script disguised as a test suite could potentially break out of the container, compromise the runner host, and access sensitive infrastructure.

To mitigate this risk, organizations need a hardened, isolated CI/CD architecture. This article provides a comprehensive, step-by-step technical blueprint for building a highly secure, isolated GitOps CI/CD system on a standard Virtual Private Server (VPS) by pairing Woodpecker CI—a lightweight, container-native automation engine—with gVisor, Google's open-source container sandbox runtime.

Why Woodpecker CI and gVisor?

Before diving into the implementation, it is vital to understand why this specific combination offers an ideal balance of performance, cost-efficiency, and robust security for a VPS deployment.

  • Woodpecker CI: A community-driven fork of Drone CI, Woodpecker is inherently container-native. Every pipeline step executes inside an isolated Docker container. It is exceptionally lightweight, consuming a fraction of the RAM and CPU required by heavier alternatives like Jenkins or GitLab CI runners, making it perfect for budget-friendly VPS environments.
  • gVisor: Standard Docker containers rely on Linux namespaces and cgroups, sharing the host kernel. If a vulnerability exists in the host kernel, a containerized process can exploit it. gVisor introduces a user-space kernel called Sentry, which intercepts and filters application system calls (syscalls). By preventing untrusted code from interacting directly with the host kernel, gVisor eliminates the vast majority of container breakout vectors.
Security Principle: Defense in depth. By combining Woodpecker's container-per-step architecture with gVisor's kernel-level virtualization, you create a zero-trust execution environment for unknown or third-party code repositories.

Architectural Overview

The architecture consists of a standard VPS running Linux (e.g., Ubuntu 24.04 LTS). The core components include:

  1. The Git Provider: GitHub, GitLab, or a self-hosted Gitea/Forgejo instance triggering webhooks on code push or pull requests.
  2. Woodpecker Server: The central orchestrator that listens for webhooks, manages user authentication, and dispatches jobs to runners.
  3. Woodpecker Agent: The execution agent configured to interface with the Docker daemon using the gVisor runtime.
  4. runsc (gVisor): The OCI-compliant runtime that intercepts syscalls for all containers spawned by the Woodpecker agent.

Step 1: Installing and Configuring gVisor on the VPS

First, we must install gVisor on the host VPS and integrate it as an available runtime within Docker. Update your system packaging tool and download the latest stable runsc binaries:

curl -fsSL [https://gvisor.dev/archive.key](https://gvisor.dev/archive.key) | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] [https://gvisor.dev/apt](https://gvisor.dev/apt) stable main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt-get update && sudo apt-get install -y runsc

Once installed, register gVisor with Docker by updating the Docker daemon configuration file located at /etc/docker/daemon.json. If the file does not exist, create it:

{
  "runtimes": {
    "runsc": {
      "path": "/usr/bin/runsc"
    }
  }
}

Restart the Docker service to apply the configuration modifications:

sudo systemctl restart docker

Verify that Docker recognizes the new sandboxed runtime by executing docker info | grep runsc. You should see runsc listed under the available runtimes.

Step 2: Deploying the Woodpecker CI Server

We will utilize Docker Compose to orchestrate both the Woodpecker Server and Agent. Create a dedicated workspace directory and define the configuration in a docker-compose.yml file:

version: '3.8'

services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:v2.4.0
    volumes:
      - ./woodpecker-server-data:/var/lib/woodpecker
    ports:
      - "8000:8000"
    environment:
      - WOODPECKER_OPEN=true
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_CLIENT=your_oauth2_client_id
      - WOODPECKER_GITEA_SECRET=your_oauth2_client_secret
      - WOODPECKER_GITEA_URL=[https://your-git-instance.com](https://your-git-instance.com)
      - WOODPECKER_AGENT_SECRET=a_very_secure_random_string_here
    restart: always

Replace the placeholders with your actual Git provider OAuth2 credentials. Run docker compose up -d woodpecker-server to initialize the orchestrator.

Step 3: Hardening the Woodpecker Agent with gVisor

To ensure that all executing workloads are isolated, the Woodpecker Agent must be instructed to spawn workflow containers using the runsc runtime. Append the agent service definition to your docker-compose.yml file:

  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v2.4.0
    command: agent
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WOODPECKER_SERVER=woodpecker-server:8000
      - WOODPECKER_AGENT_SECRET=a_very_secure_random_string_here
      - WOODPECKER_BACKEND=docker
      - WOODPECKER_DOCKER_RUNTIME=runsc
    restart: always
    depends_on:
      - woodpecker-server

The critical configuration parameter here is WOODPECKER_DOCKER_RUNTIME=runsc. This environment variable forces the Woodpecker backend allocator to specify gVisor as the OCI execution runtime for every pipeline phase container it creates.

Deploy the agent by running docker compose up -d.

Step 4: Crafting a Secure GitOps Pipeline

With the infrastructure locked down, you can now safely ingest unvetted repositories. Create a .woodpecker.yml configuration file inside the root directory of the target repository to define your automated workflow:

pipeline:
  security-scan:
    image: secure-scanner-image:latest
    commands:
      - echo "Running static application security testing (SAST)..."
      - scan-dependencies --path .

  test:
    image: python:3.11-slim
    commands:
      - pip install -r requirements.txt
      - pytest tests/

  gitops-deploy:
    image: alpine/git:latest
    commands:
      - echo "Updating Kubernetes manifest repository..."
      - git clone [https://github.com/org/manifest-repo.git](https://github.com/org/manifest-repo.git)
      - cd manifest-repo
      - sed -i "s|image:.*|image: myapp:${CI_COMMIT_SHA}|" deployment.yaml
      - git add deployment.yaml
      - git commit -m "Deploying commit ${CI_COMMIT_SHA} via isolated CI/CD"
      - git push origin main
    when:
      branch: main
      event: push

Even if an untrusted collaborator submits a pull request containing malicious Python test scripts designed to read files from the host server (e.g., /etc/passwd), gVisor will intercept the unauthorized kernel interaction, safely bounding the execution within the virtual user-space sandbox environment.

Conclusion and Best Practices

By implementing Woodpecker CI alongside gVisor on a cost-effective VPS, you achieve an enterprise-grade, secure GitOps CI/CD engine tailored for handling unknown or potentially dangerous codebases. To maintain a pristine security posture over time, adhere to these operational guidelines:

  • Keep gVisor updated: Frequently run sudo apt update && sudo apt upgrade runsc to ensure protection against newly discovered hardware or virtualization bypasses.
  • Restrict Agent Volumes: Never mount sensitive host paths into the Woodpecker agent container or allow pipelines to run in privileged mode unless absolutely necessary.
  • Network Isolation: Utilize restrictive Docker bridge network setups to prevent sandboxed containers from scanning or probing your local private VPS network networks.
Building an Isolated GitOps CI/CD Pipeline for Untrusted Code Using Woodpecker CI and gVisor on a VPS | DPTCloud