Guide · CI/CD

MCP Servers with Tekton Pipelines — cloud-native Kubernetes CI/CD and protocol verification

Tekton is a Kubernetes-native CI/CD framework implemented entirely as Kubernetes Custom Resource Definitions. There is no separate CI server to provision, no external webhook relay to configure, and no dependency on a managed SaaS platform — the pipeline runs as ordinary Kubernetes Pods, using the same RBAC, networking, and storage primitives as your MCP server workload. For teams already running MCP servers on Kubernetes, Tekton offers the most operationally cohesive CI/CD approach: the build and deploy pipeline is co-located with the workload it manages, uses the same secrets store, and is observable through the same tooling. This guide covers Tekton's core CRDs, buildah for daemonless OCI image builds, workspace management, a complete Pipeline for MCP server CI/CD, and a custom Task that verifies the MCP protocol after every deploy.

TL;DR

Install Tekton Pipelines in your cluster, define a Pipeline with four Tasks (git-clone → build-push via buildah → kubectl-set-image → mcp-verify), wire it to a Git webhook via an EventListener and TriggerTemplate, and add a final step in the verify Task that calls AliveMCP's API to register the new deployment — all without leaving the Kubernetes ecosystem.

Why Tekton for MCP server teams in Kubernetes-heavy organisations

The decisive argument for Tekton is operational uniformity. When your MCP server already runs in Kubernetes and your team already understands Kubernetes RBAC, namespaces, Secrets, PersistentVolumeClaims, and ServiceAccounts, Tekton adds CI/CD without introducing a new operational domain. The pipeline configuration is YAML applied with kubectl apply, secrets are Kubernetes Secrets accessed through the same mechanisms as application secrets, and pipeline execution is observable via kubectl get pipelineruns and the Tekton Dashboard — the same cluster-level access you already have.

In contrast, connecting an external CI system (GitHub Actions, GitLab CI, Jenkins) to a Kubernetes cluster requires exporting cluster credentials (a kubeconfig or service account token), storing them as secrets in the external system, and managing the inbound network path for webhooks. Each of these steps is a security surface and an operational dependency. Tekton eliminates them: the pipeline runs inside the cluster and uses in-cluster service accounts with only the permissions it needs.

The trade-off is setup complexity. Tekton requires installing the Tekton Pipelines CRDs plus optionally the Tekton Triggers CRDs for webhook-driven execution. There is no managed SaaS option for on-premise Kubernetes (though Tekton is available as a component in OpenShift Pipelines). For teams already running Kubernetes for their MCP server, the setup cost is bounded and one-time; for teams not yet on Kubernetes, Tekton is not the right starting point — use GitHub Actions or GitLab CI instead.

Core CRDs: Task, Pipeline, PipelineRun, and Triggers

Tekton's object model has five primary CRDs. A Task is the unit of work: a list of steps, each of which is a container image and a command. Steps in a Task run sequentially in the same Pod, sharing an emptyDir volume mounted at /workspace. This shared workspace is what allows one step to clone source code and a subsequent step to build it. Tasks accept params (string/array/object values passed at runtime) and workspaces (references to volumes or Secrets that the Task can read and write).

A Pipeline is an ordered graph of Task references. Tasks in a Pipeline run in separate Pods; they share data through workspace volumes (typically PersistentVolumeClaims) rather than through the local file system. The Pipeline defines which tasks depend on which others via runAfter:, and it maps Pipeline-level params and workspaces down to individual Task invocations. A Pipeline does not run on its own — it is a reusable template.

A PipelineRun instantiates a Pipeline with concrete parameter values and workspace bindings. A PipelineRun creates TaskRuns, which create Pods. The PipelineRun's status tracks the execution state of each TaskRun and the overall pipeline result. You can create a PipelineRun manually with kubectl apply or tkn pipeline start, or automatically via Tekton Triggers.

The Tekton Triggers system provides an EventListener (a Service that receives webhook POST requests), a TriggerBinding (extracts fields from the event payload — e.g., the commit SHA, the branch name, the repository URL), and a TriggerTemplate (a template for a PipelineRun that uses the extracted fields as parameter values). Together they form the webhook-to-pipeline wiring: a git push event hits the EventListener URL, the TriggerBinding extracts $body.after as the commit SHA and $body.ref as the branch, and the TriggerTemplate creates a PipelineRun with those values. The EventListener runs as a Deployment in the cluster and needs an Ingress or LoadBalancer Service to be reachable from the Git provider's webhook delivery IP ranges.

Building OCI images with buildah (no Docker daemon required)

Building container images inside a Kubernetes Pod is non-trivial because the Pod cannot access the node's Docker daemon (and should not — that would give it root on the node). Tekton's recommended solution is buildah, an OCI-compliant image builder that operates entirely in user space without requiring a Docker daemon or privileged access. Buildah reads a Dockerfile (or Containerfile), executes the build steps as unprivileged processes, and pushes the result to any OCI registry.

The Tekton Hub provides a pre-built buildah Task (hub.tekton.dev/task/buildah) that you install via tkn hub install task buildah or by applying the YAML directly. The key parameters are IMAGE (the full image reference to push to), DOCKERFILE (the path relative to the source workspace), and CONTEXT (the build context directory). The Task also requires a workspace named source (the cloned repository) and a Secret containing registry credentials mounted via the workspace name dockerconfig.

An alternative to buildah is kaniko, which is also daemonless. The practical difference: buildah can run as a non-root user on recent kernels with user namespace support, while kaniko requires running as root but not as privileged. For clusters with strict pod security admission policies, buildah with user namespaces is often the more permissible choice. Both can push to any OCI registry — Google Artifact Registry, Amazon ECR, Docker Hub, or a private Harbor instance.

The registry credentials Secret must be a kubernetes.io/dockerconfigjson type Secret, which Tekton's buildah Task mounts as a Docker config file. You create it with kubectl create secret docker-registry registry-credentials --docker-server=registry.example.com --docker-username=user --docker-password=token. The ServiceAccount used by the Tekton TaskRun can also reference the Secret via imagePullSecrets, which handles pulling from private registries during the kubectl-set-image deploy step.

Workspaces, PVCs, and RBAC for the Pipeline

In a Tekton Pipeline, Tasks run in separate Pods and cannot share an emptyDir — they need a PersistentVolumeClaim that multiple Pods can mount. The Pipeline declares a workspace, and the PipelineRun binds it to a PVC. The git-clone Task writes the repository to the PVC; the buildah Task reads it from the same PVC. The PVC must use a storage class that supports ReadWriteOnce access mode at a minimum, or ReadWriteMany if parallel tasks need to read the source simultaneously.

The ServiceAccount for Tekton TaskRuns needs specific RBAC permissions. At minimum, the ServiceAccount needs: permission to get and patch Deployments in the target namespace (for the deploy task), permission to read Secrets in the Tekton namespace (for registry credentials), and permission to create PipelineRuns (if the EventListener creates them). The principle of least privilege applies: create a dedicated ServiceAccount for Tekton pipeline runs, bind it to a Role with only the needed permissions in the target namespace, and avoid using the default ServiceAccount which may have other bindings.

Complete Pipeline YAML for MCP server CI/CD

The following manifests define a complete Pipeline with four Tasks: git-clone, build-and-push (buildah), deploy (kubectl), and the custom MCP protocol verification Task.

---
# Custom Task: verify MCP protocol after deploy
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: mcp-verify
  namespace: tekton-pipelines
spec:
  params:
    - name: mcp-url
      type: string
      description: Full URL of the MCP endpoint to probe
    - name: alivemcp-api-key
      type: string
      description: AliveMCP API key for registration
    - name: server-slug
      type: string
      description: AliveMCP server slug
    - name: image-digest
      type: string
      description: Image digest just deployed (for AliveMCP metadata)
  steps:
    - name: probe-mcp-protocol
      image: curlimages/curl:8.7.1
      script: |
        #!/bin/sh
        set -euo pipefail
        echo "Probing MCP protocol at $(params.mcp-url)"
        # Allow time for the rollout to complete
        sleep 15
        RESPONSE=$(curl -sf --max-time 20 \
          -X POST "$(params.mcp-url)" \
          -H "Content-Type: application/json" \
          -d '{
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
              "protocolVersion": "2025-03-26",
              "capabilities": {},
              "clientInfo": { "name": "tekton-probe", "version": "1.0" }
            }
          }')
        echo "Response: $RESPONSE"
        PROTO=$(echo "$RESPONSE" | grep -o '"protocolVersion":"[^"]*"' \
          | sed 's/"protocolVersion":"//;s/"//')
        if [ -z "$PROTO" ]; then
          echo "ERROR: MCP initialize did not return a protocolVersion"
          exit 1
        fi
        echo "MCP protocol version: $PROTO"
        echo "Protocol verification PASSED"

    - name: register-alivemcp
      image: curlimages/curl:8.7.1
      script: |
        #!/bin/sh
        curl -sf \
          -X POST "https://alivemcp.com/api/register" \
          -H "Authorization: Bearer $(params.alivemcp-api-key)" \
          -H "Content-Type: application/json" \
          -d "{
            \"slug\": \"$(params.server-slug)\",
            \"url\": \"$(params.mcp-url)\",
            \"version\": \"$(params.image-digest)\"
          }" \
          && echo "AliveMCP registration successful" \
          || echo "WARNING: AliveMCP registration failed (non-fatal)"

---
# The main CI/CD Pipeline
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: mcp-server-pipeline
  namespace: tekton-pipelines
spec:
  params:
    - name: git-url
      type: string
    - name: git-revision
      type: string
      default: main
    - name: image
      type: string
      description: Full image reference e.g. registry.example.com/myorg/mcp-server
    - name: mcp-url
      type: string
    - name: deploy-namespace
      type: string
      default: default
    - name: deployment-name
      type: string
    - name: container-name
      type: string
      default: mcp-server
    - name: server-slug
      type: string
  workspaces:
    - name: source
      description: PVC for sharing source code between tasks
    - name: registry-credentials
      description: Docker config secret for registry auth

  tasks:
    # ── 1. Clone the repository ───────────────────────────────
    - name: clone
      taskRef:
        resolver: hub
        params:
          - name: catalog
            value: tekton-catalog-tasks
          - name: type
            value: artifact
          - name: kind
            value: task
          - name: name
            value: git-clone
          - name: version
            value: "0.9"
      params:
        - name: url
          value: $(params.git-url)
        - name: revision
          value: $(params.git-revision)
      workspaces:
        - name: output
          workspace: source

    # ── 2. Build and push the OCI image ──────────────────────
    - name: build-push
      runAfter:
        - clone
      taskRef:
        resolver: hub
        params:
          - name: catalog
            value: tekton-catalog-tasks
          - name: type
            value: artifact
          - name: kind
            value: task
          - name: name
            value: buildah
          - name: version
            value: "0.7"
      params:
        - name: IMAGE
          value: $(params.image):$(params.git-revision)
        - name: DOCKERFILE
          value: ./Dockerfile
        - name: CONTEXT
          value: .
      workspaces:
        - name: source
          workspace: source
        - name: dockerconfig
          workspace: registry-credentials

    # ── 3. Deploy to Kubernetes ───────────────────────────────
    - name: deploy
      runAfter:
        - build-push
      taskSpec:
        params:
          - name: image
            type: string
          - name: namespace
            type: string
          - name: deployment
            type: string
          - name: container
            type: string
        steps:
          - name: kubectl-set-image
            image: bitnami/kubectl:1.30
            script: |
              #!/bin/bash
              set -euo pipefail
              kubectl set image deployment/$(params.deployment) \
                $(params.container)=$(params.image) \
                --namespace $(params.namespace)
              kubectl rollout status deployment/$(params.deployment) \
                --namespace $(params.namespace) \
                --timeout=5m
      params:
        - name: image
          value: $(params.image):$(params.git-revision)
        - name: namespace
          value: $(params.deploy-namespace)
        - name: deployment
          value: $(params.deployment-name)
        - name: container
          value: $(params.container-name)

    # ── 4. Verify MCP protocol ────────────────────────────────
    - name: verify
      runAfter:
        - deploy
      taskRef:
        name: mcp-verify
      params:
        - name: mcp-url
          value: $(params.mcp-url)
        - name: alivemcp-api-key
          value: "$(tasks.deploy.results.alivemcp-key)"  # or mount from secret
        - name: server-slug
          value: $(params.server-slug)
        - name: image-digest
          value: $(tasks.build-push.results.IMAGE_DIGEST)

The runAfter: field enforces the sequential execution order. Tekton will not start the deploy task until build-push completes and produces a valid image digest in its task result. The tasks.build-push.results.IMAGE_DIGEST reference propagates the immutable digest from the buildah task into the verify task, so AliveMCP records the exact content-addressable digest rather than a mutable tag.

EventListeners, Triggers, and the tkn CLI

An EventListener is a Deployment that exposes an HTTP endpoint. When your Git provider sends a push webhook to that endpoint, the Tekton Triggers controller processes the event body through a TriggerBinding to extract parameters and creates a PipelineRun via a TriggerTemplate. The EventListener needs a ClusterRoleBinding allowing it to create PipelineRuns and the associated resources. The tkn CLI is the primary tool for inspecting pipeline execution: tkn pipelinerun list shows recent runs, tkn pipelinerun logs -f streams logs from all TaskRuns in a pipeline, and tkn taskrun describe gives step-level detail including exit codes and timestamps.

Tekton Chains is the supply chain security extension: it observes TaskRun completions and automatically signs the task results (including image digests produced by buildah) using Sigstore's cosign or in-toto attestations. For MCP server teams with compliance requirements around build provenance, enabling Tekton Chains provides a cryptographic audit trail from source commit to running container without any changes to the pipeline YAML.

Comparing Tekton to ArgoCD is a common point of confusion: they are complementary, not alternatives. Tekton is a CI and CD pipeline runner — it executes build, test, and deploy tasks in response to events. ArgoCD is a GitOps reconciler — it continuously compares the desired state in a Git repository against the actual state in the cluster and applies differences. A common pattern is to use Tekton to build and push the image, then update the image tag in a Helm values file or Kustomize overlay, and let ArgoCD detect that Git change and reconcile it to the cluster. Tekton handles the event-driven build phase; ArgoCD handles the continuous reconciliation phase.

Frequently asked questions

How is Tekton different from ArgoCD for MCP server deployments?

Tekton is a pipeline execution engine: it runs a sequence of Tasks (each in a container) in response to events like Git pushes. It handles building images, running tests, and applying Kubernetes manifests. ArgoCD is a GitOps reconciliation controller: it watches a Git repository for changes to Kubernetes manifests and continuously applies those changes to the cluster, with automatic drift detection and optional auto-sync. They solve different problems. Tekton answers "how do I build and deploy when code changes?" — it is event-driven and imperative (run these steps in order). ArgoCD answers "how do I keep the cluster state consistent with what is declared in Git?" — it is continuous and declarative. In practice, many teams use both: Tekton builds the image and updates the image tag in the GitOps repository, then ArgoCD picks up that change and reconciles the cluster. For a simple MCP server deployment, Tekton alone (doing the kubectl rollout) or ArgoCD alone (watching for image tag changes) is sufficient. The combination makes most sense when you have many services with complex deployment policies.

Do I need privileged containers to build Docker images with Tekton?

With buildah and user namespaces, no. Buildah can build OCI images in an unprivileged container on Linux kernels 5.12+ with user namespace support enabled. The buildah Tekton Hub Task supports a BUILD_EXTRA_ARGS parameter where you can pass --isolation=chroot for environments where user namespaces are not available, though this does require the container to run as root (not privileged). The key distinction is: root inside a container with no Linux capabilities is much safer than a privileged container that has all capabilities and can escape to the host. Kaniko is an alternative that runs as root without needing user namespaces and without requiring any additional Linux capabilities, making it more compatible with strict PodSecurityAdmission policies (restricted profile). For clusters running Kubernetes 1.25+ with the restricted pod security standard, kaniko is often the easiest path because it requires only a single capability adjustment (allowPrivilegeEscalation: false) rather than user namespace configuration.

How do I pass secrets (like a registry password or SSH key) to Tekton Tasks?

Tekton accesses Kubernetes Secrets through workspaces or environment variable injection. The recommended pattern is to create a Kubernetes Secret and reference it as a workspace in the Task and Pipeline definitions — the Secret is mounted as a volume at a specified path within the Task's container, and the Task script reads the file. For Docker registry credentials, create a kubernetes.io/dockerconfigjson Secret and pass it as the dockerconfig workspace to the buildah Task. For other secrets (API keys, SSH keys), create a generic Secret with the key-value pairs you need and map them to environment variables using a secretKeyRef in the TaskRun's stepTemplate.env. Avoid embedding secrets directly in Pipeline or Task params, which would expose them in the PipelineRun YAML and Tekton's task result storage. The AliveMCP API key used in the mcp-verify Task should be stored in a Kubernetes Secret and referenced via a workspace or environment variable, not passed as a plain pipeline parameter.

How do I observe and debug a failed Tekton PipelineRun?

Start with tkn pipelinerun describe <run-name> -n tekton-pipelines for a summary of which Tasks passed and which failed. Then use tkn pipelinerun logs <run-name> -n tekton-pipelines to see the full log output of all steps. For a specific failed Task, tkn taskrun describe <taskrun-name> shows the exit codes and status conditions for each step container. The Tekton Dashboard (a web UI installed separately) provides a visual graph of the pipeline with color-coded task status and clickable log streams — essential for debugging complex pipelines without memorising CLI commands. For the mcp-verify Task specifically, common failure modes are: the MCP server took longer than the sleep threshold to become ready (increase the sleep value), the MCP server URL is wrong in the Pipeline params, or the initialize response body did not match the expected structure (the grep for protocolVersion failed). Check the probe step's log output for the raw response body to distinguish these cases.

Can I use Tekton with a hosted Kubernetes service like GKE, EKS, or AKS?

Yes — Tekton runs on any CNCF-conformant Kubernetes cluster, including GKE, EKS, AKS, DigitalOcean Kubernetes, and self-hosted K3s or RKE. Install Tekton Pipelines with kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml and optionally Tekton Triggers for webhook support. On GKE, Workload Identity is the recommended way to give Tekton TaskRuns access to Google Artifact Registry and other GCP services without storing service account JSON keys as Kubernetes Secrets. On EKS, use IRSA (IAM Roles for Service Accounts) to grant the Tekton ServiceAccount access to ECR and other AWS services. On AKS, use AAD Pod Identity or Workload Identity for Azure Container Registry access. The mcp-verify Task is cluster-agnostic — it uses curlimages/curl and makes outbound HTTP calls to your MCP server URL and to alivemcp.com, which works on any cluster with outbound internet access.

Further reading

Know when your MCP server is down — before users do

AliveMCP probes your server's MCP endpoint every minute, detects protocol errors and transport failures, and pages you before users notice.

Start monitoring free