Table of Contents
Start your free trial.
Start your free trial.
Start your free trial.




Table of Contents
Executive Summary
Introduction
Modern software teams rely on CI/CD pipelines to run tests automatically when code changes. GitHub Actions has become a popular choice, but it tightly couples your test automation to a single Git vendor, requires outbound webhook connectivity, and forces teams to manage YAML-based pipeline definitions outside their testing infrastructure.
Testkube Git Triggers offer a fundamentally different approach. By polling Git repositories directly from inside your Kubernetes cluster, Testkube enables the same event-driven test automation, without webhooks, without vendor lock-in, and without internet access.
This document compares Testkube Git Triggers with GitHub Actions across key scenarios, demonstrates how git-push, git-tag-push, and git-pull-request events work with any Git provider, and shows why Testkube is the superior choice for teams running in air-gapped, multi-cloud, or hybrid environments.
How Testkube Git Triggers work
Testkube Git Triggers monitor Git repositories using a polling-based architecture. A component called the Git Informer runs inside your Kubernetes cluster and periodically checks configured repositories for new commits, tags, and pull requests.
Core architecture
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Git Informer │────▶│ Trigger │───▶│ TestWorkflow │ │
│ │ (Polling) │ │ Matcher │ │ Execution │ │
│ └──────┬───────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ │ Polls every 1 min (configurable) │
└─────────┼───────────────────────────────────────────────────┘
│
▼
┌──────────────┐
│ Any Git Repo │ (GitHub, GitLab, Bitbucket, Gitea,
│ (Remote) │ self-hosted, air-gapped)
└──────────────┘Supported events
Key differentiators
- No webhooks required. Testkube polls repositories, eliminating the need for inbound network access
- Pure Go implementation. Uses the
go-gitlibrary natively; no externalgitbinary needed - Leader-elected. In multi-replica deployments, only one instance polls to avoid duplicate events
- Rich metadata injection. Git context (commit SHA, branch, tag, PR details) is automatically available in your test workflows
Side-by-side comparison: Testkube Git Triggers vs. GitHub Actions
At a glance
Scenario 1: run tests on every push to main
GitHub Actions
# .github/workflows/test-on-push.yml
name: Run Tests on Push
on:
push:
branches:
- main
paths:
- 'src/**'
- 'tests/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm install
npm testLimitations:
- Tied to GitHub, moving to GitLab or Bitbucket requires a complete rewrite
- Tests run on ephemeral GitHub runners, not in your actual deployment environment
- No access to in-cluster services, databases, or APIs without complex tunneling
Testkube Git Triggers
# TestTrigger CRD
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: test-on-main-push
namespace: testkube
spec:
resource: content
event: git-push
contentSelector:
git:
uri: https://github.com/your-org/your-repo.git
branches:
- main
paths:
- "src/**"
- "tests/**"
tokenFrom:
secretKeyRef:
name: git-credentials
key: token
action: run
execution: testworkflow
testSelector:
name: integration-tests
namespace: testkube
concurrencyPolicy: replace# TestWorkflow
apiVersion: testworkflows.testkube.io/v1
kind: TestWorkflow
metadata:
name: integration-tests
namespace: testkube
spec:
content:
git:
uri: https://github.com/your-org/your-repo.git
revision: "{{ config.TESTKUBE_GIT_COMMIT }}"
steps:
- name: Run Tests
run:
image: node:20
shell: |
cd /data/repo
npm install
npm testAdvantages:
- Tests run inside your Kubernetes cluster with access to real services, databases, and APIs
- The same trigger works with any Git provider, just change the
uri - Git metadata (
TESTKUBE_GIT_COMMIT,TESTKUBE_GIT_BRANCH) is automatically injected concurrencyPolicy: replaceensures only the latest push is tested
Scenario 2: release validation on tag push
GitHub Actions
# .github/workflows/release-tests.yml
name: Release Validation
on:
push:
tags:
- 'v*'
jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: ./deploy.sh staging
- name: Run smoke tests
run: ./run-smoke-tests.sh
- name: Run performance tests
run: ./run-perf-tests.shLimitations:
- Cannot easily deploy to a real staging cluster from GitHub runners
- Performance tests on shared GitHub infrastructure produce unreliable results
- No native integration with Kubernetes deployments
Testkube Git Triggers
# TestTrigger CRD
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: release-validation
namespace: testkube
spec:
resource: content
event: git-tag-push
contentSelector:
git:
uri: https://github.com/your-org/your-repo.git
tags:
- "v*"
tagsIgnore:
- "v*-rc*"
- "v*-beta*"
tokenFrom:
secretKeyRef:
name: git-credentials
key: token
action: run
execution: testworkflow
testSelector:
name: release-smoke-tests
namespace: testkube# TestWorkflow for release validation
apiVersion: testworkflows.testkube.io/v1
kind: TestWorkflow
metadata:
name: release-smoke-tests
namespace: testkube
spec:
steps:
- name: Deploy to Staging
run:
image: bitnami/kubectl:latest
shell: |
kubectl set image deployment/myapp \
myapp=myregistry/myapp:{{ config.TESTKUBE_GIT_TAG }}
kubectl rollout status deployment/myapp --timeout=120s
- name: Smoke Tests
run:
image: grafana/k6:latest
shell: |
k6 run /data/tests/smoke.js
- name: Performance Tests
run:
image: grafana/k6:latest
shell: |
k6 run /data/tests/performance.jsAdvantages:
- Tag name is available via
TESTKUBE_GIT_TAG. Use it to deploy the exact version - Smoke and performance tests run against real infrastructure in your cluster
- Exclude pre-release tags (
v*-rc*,v*-beta*) withtagsIgnorepatterns - Performance test results are consistent. No shared runner variability
Scenario 3: multi-service monorepo testing
GitHub Actions
# .github/workflows/service-tests.yml
name: Service Tests
on:
push:
branches: [main, 'release/*']
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.changes.outputs.api }}
web: ${{ steps.changes.outputs.web }}
steps:
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
api:
- 'services/api/**'
web:
- 'services/web/**'
test-api:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.api == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/api && go test ./...
test-web:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.web == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/web && npm testLimitations:
- Complex change detection logic with third-party actions
- Conditional job execution adds pipeline complexity
- Each service test runs in isolation, unable to test cross-service interactions
Testkube Git Triggers
# One trigger per service — path filtering is built in
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: api-service-trigger
namespace: testkube
spec:
resource: content
event: git-push
contentSelector:
git:
uri: https://github.com/your-org/monorepo.git
branches:
- main
- "release/*"
paths:
- "services/api/**"
pathsIgnore:
- "services/api/**/*.md"
- "services/api/docs/**"
action: run
execution: testworkflow
testSelector:
name: api-service-tests
namespace: testkube
---
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: web-service-trigger
namespace: testkube
spec:
resource: content
event: git-push
contentSelector:
git:
uri: https://github.com/your-org/monorepo.git
branches:
- main
- "release/*"
paths:
- "services/web/**"
pathsIgnore:
- "services/web/**/*.md"
action: run
execution: testworkflow
testSelector:
name: web-service-tests
namespace: testkubeAdvantages:
- Native path filtering. No third-party actions or change detection hacks
- Each trigger is independent and declarative, easy to understand and maintain
- Services can be tested in isolation or together with cross-service integration tests
pathsIgnoreensures documentation-only changes don't trigger test runs
Scenario 4: air-gapped and self-hosted Git environments
This is where Testkube Git Triggers truly shine, and where GitHub Actions simply cannot operate.
The problem with GitHub Actions in restricted environments
GitHub Actions fundamentally requires:
- Outbound internet access to reach GitHub's API and download actions
- Webhook delivery from GitHub to trigger workflows
- GitHub-hosted runners or self-hosted runners with GitHub connectivity
- GitHub as your Git provider. It does not work with GitLab, Bitbucket, or self-hosted Git
For organizations in defense, healthcare, finance, or government sectors, these requirements are often non-starters.
Testkube Git Triggers: built for air-gapped environments
# Works with any internal Git server — no internet required
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: air-gapped-trigger
namespace: testkube
spec:
resource: content
event: git-push
contentSelector:
git:
uri: git@git.internal.corp:team/secure-app.git
branches:
- main
- "release/*"
sshKeyFrom:
secretKeyRef:
name: internal-git-ssh
key: private-key
action: run
execution: testworkflow
testSelector:
name: security-compliance-tests
namespace: testkubeWhy it works without internet
Supported Git providers
Testkube Git Triggers work identically across all Git providers:
The same trigger YAML works with every provider, only the uri and credentials change.
Scenario 5: tag-based release pipeline across Git vendors
A common CI/CD pattern is triggering release workflows when a version tag is pushed. With GitHub Actions, this only works on GitHub. With Testkube, it works everywhere.
GitHub Actions (GitHub only)
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'Testkube Git Triggers (any Git vendor)
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: release-pipeline
namespace: testkube
spec:
resource: content
event: git-tag-push
contentSelector:
git:
# Works with ANY Git server
uri: https://gitlab.internal.com/platform/core-service.git
tags:
- "v*"
tagsIgnore:
- "v*-dev"
- "v*-snapshot"
tokenFrom:
secretKeyRef:
name: gitlab-credentials
key: token
action: run
execution: testworkflow
testSelector:
nameRegex: "release-.*"
namespace: testkubeWhen a tag like v2.5.0 is pushed to any Git server, Testkube:
- Detects the tag via polling (no webhook needed)
- Injects metadata:
TESTKUBE_GIT_TAG=v2.5.0,TESTKUBE_GIT_COMMIT=abc123,TESTKUBE_GIT_REF=refs/tags/v2.5.0 - Runs all matching TestWorkflows: e.g.,
release-smoke-tests,release-integration-tests,release-security-scan - Reports results in the Testkube Dashboard with full traceability
Scenario 6: pull request validation, keep GitHub, run tests in your own environment
Many teams are deeply integrated with GitHub. Their workflows, code reviews, branch protection rules, and developer tooling all revolve around GitHub Pull Requests. Moving away from GitHub entirely is impractical or undesirable. However, running test workloads on GitHub-hosted runners means tests execute outside your real infrastructure, without access to internal services, databases, or production-like environments.
Testkube's git-pull-request event bridges this gap: you keep GitHub as your source-of-truth for code and collaboration, while Testkube runs your PR validation tests inside your own Kubernetes cluster, against real services, with real data, in a real network topology.
The problem: GitHub Actions PR tests run in isolation
# .github/workflows/pr-tests.yml
name: PR Validation
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start dependencies
run: docker-compose up -d # Simulated services, not real ones
- name: Run integration tests
run: |
npm install
npm run test:integration
- name: Run E2E tests
run: npm run test:e2e # Against mocked APIs, not real clusterLimitations of this approach:
- Tests run against mocked or containerized services, not your real infrastructure
- No access to internal APIs, databases, or message queues behind your firewall
- Docker-compose "mini environments" diverge from production. Passing tests don't guarantee production readiness
- Self-hosted runners require exposing your infrastructure to GitHub's webhook delivery network
- GitHub-hosted runners add latency (provisioning, dependency installation) on every PR update
- Cannot test Kubernetes-specific behaviors (network policies, service mesh, RBAC, resource limits)
Testkube git-pull-request event: GitHub for code, your cluster for tests
# TestTrigger CRD — reacts to GitHub Pull Requests
apiVersion: tests.testkube.io/v1
kind: TestTrigger
metadata:
name: pr-validation
namespace: testkube
spec:
resource: content
event: git-pull-request
contentSelector:
git:
uri: https://github.com/your-org/your-repo.git
pullRequest:
branches:
- main # Only PRs targeting main
paths:
- "src/**"
- "tests/**"
- "api/**"
pathsIgnore:
- "**/*.md"
- "docs/**"
tokenFrom:
secretKeyRef:
name: github-token
key: token
action: run
execution: testworkflow
testSelector:
name: pr-integration-tests
namespace: testkube
concurrencyPolicy: replace # Only test the latest PR commit# TestWorkflow — runs PR validation inside your cluster
apiVersion: testworkflows.testkube.io/v1
kind: TestWorkflow
metadata:
name: pr-integration-tests
namespace: testkube
spec:
content:
git:
uri: https://github.com/your-org/your-repo.git
revision: "{{ config.TESTKUBE_GIT_PR_HEAD_SHA }}"
tokenFrom:
secretKeyRef:
name: github-token
key: token
steps:
- name: Integration Tests Against Real Services
run:
image: node:20
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: test-db-credentials
key: url
- name: API_GATEWAY_URL
value: "http://api-gateway.staging.svc.cluster.local:8080"
shell: |
echo "Testing PR #{{ config.TESTKUBE_GIT_PR_NUMBER }}: {{ config.TESTKUBE_GIT_PR_TITLE }}"
echo "Branch: {{ config.TESTKUBE_GIT_PR_HEAD_REF }} → {{ config.TESTKUBE_GIT_PR_BASE_REF }}"
cd /data/repo
npm install
npm run test:integration # Tests against REAL database
npm run test:e2e # Tests against REAL API gateway
- name: Kubernetes-Specific Validation
run:
image: bitnami/kubectl:latest
shell: |
# Deploy the PR branch to a preview namespace
kubectl create namespace preview-pr-{{ config.TESTKUBE_GIT_PR_NUMBER }} --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f k8s/manifests/ -n preview-pr-{{ config.TESTKUBE_GIT_PR_NUMBER }}
kubectl rollout status deployment/app -n preview-pr-{{ config.TESTKUBE_GIT_PR_NUMBER }} --timeout=120s
- name: Performance Baseline Check
run:
image: grafana/k6:latest
shell: |
# Run performance tests against the preview deployment
k6 run --out json=/data/artifacts/perf-results.json \
/data/repo/tests/performance/baseline.jsHow it works
- Developer opens or updates a PR on GitHub: normal GitHub workflow, no changes needed
- Testkube's Git Informer detects the PR event via polling (no webhook configuration required)
- Trigger matches: the PR targets
mainand changes files insrc/**,tests/**, orapi/** - TestWorkflow executes inside your cluster: with full access to internal services, databases, and APIs
- Rich PR metadata is injected: PR number, title, author, head/base refs, head SHA, and PR URL are all available as
config.*variables - Results appear in the Testkube Dashboard: with full test logs, artifacts, and traceability back to the PR
Why this matters for GitHub-linked teams
The hybrid model: best of both worlds
With git-pull-request, teams get the best of both ecosystems:
- Keep GitHub for what it does best: code hosting, pull request reviews, issue tracking, branch protection, team collaboration
- Use Testkube for what it does best: running tests inside your actual infrastructure with access to real services, real data, and real network conditions
This means:
- Developers continue using their familiar GitHub PR workflow, no behavior change
- Tests validate against production-like environments, not mocked services
- Sensitive test data (credentials, customer data, internal APIs) never leaves your network
- No webhook endpoints to configure or secure. Testkube polls GitHub via standard Git protocols
- Works even if your cluster has no inbound internet access. Only outbound HTTPS to github.com is needed
- PR metadata (number, title, author, branch names) flows into tests automatically for reporting and traceability
Example: complete PR workflow with status reporting
For teams that want PR check status reported back to GitHub, Testkube can be paired with a simple status update step:
steps:
- name: Run Tests
run:
image: node:20
shell: |
cd /data/repo && npm test
- name: Report Status to GitHub
condition: always
run:
image: curlimages/curl:latest
env:
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: github-token
key: token
shell: |
STATUS=$([ "{{ status }}" = "passed" ] && echo "success" || echo "failure")
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/your-org/your-repo/statuses/{{ config.TESTKUBE_GIT_PR_HEAD_SHA }}" \
-d "{\"state\":\"$STATUS\",\"target_url\":\"https://app.testkube.io/executions/{{ execution.id }}\",\"description\":\"Testkube PR Validation\",\"context\":\"testkube/pr-tests\"}"This posts a commit status back to GitHub, so the PR shows a green/red check, maintaining full visibility in the GitHub UI while tests execute in your environment.
Git metadata: context available in your tests
Every triggered TestWorkflow receives rich Git context as environment variables, enabling dynamic test behavior:
Standard Git metadata
Pull request metadata (GitHub repos)
Using metadata in TestWorkflows
steps:
- name: Test against correct commit
run:
image: golang:1.22
shell: |
echo "Testing commit: {{ config.TESTKUBE_GIT_COMMIT }}"
echo "Branch: {{ config.TESTKUBE_GIT_BRANCH }}"
echo "Tag: {{ config.TESTKUBE_GIT_TAG }}"
git checkout {{ config.TESTKUBE_GIT_COMMIT }}
go test ./...Advanced capabilities
Concurrency policies
Control how triggers behave when multiple pushes arrive in quick succession:
Branch and tag glob patterns
contentSelector:
git:
branches:
- main
- "release/*" # Matches release/1.0, release/2.0, etc.
- "feature/**" # Matches feature/auth, feature/api/v2, etc.
branchesIgnore:
- "dependabot/**" # Ignore automated dependency updates
tags:
- "v[0-9]*" # Matches v1.0.0, v2.1.3, etc.
tagsIgnore:
- "v*-rc*" # Ignore release candidatesPath-based filtering
Only trigger when relevant files change, avoid unnecessary test runs:
contentSelector:
git:
paths:
- "src/**"
- "tests/**"
- "go.mod"
- "go.sum"
pathsIgnore:
- "**/*.md"
- "docs/**"
- ".github/**"
- "LICENSE"Configurable polling behavior
Fine-tune the Git Informer for your environment:
Migration guide: from GitHub Actions to Testkube Git Triggers
Step 1: identify your triggers
Map your GitHub Actions on: blocks to Testkube events:
Step 2: convert path filters
Step 3: move secrets to Kubernetes
# GitHub Secret → Kubernetes Secret
kubectl create secret generic my-test-secrets \
--from-literal=API_KEY=your-api-key \
--from-literal=DB_PASSWORD=your-db-pass \
-n testkubeStep 4: create TestWorkflows
Convert your GitHub Actions job steps into Testkube TestWorkflow steps. Each run: block maps to a TestWorkflow step with the appropriate container image.
Step 5: deploy TestTrigger CRDs
Apply your trigger definitions to your Kubernetes cluster:
kubectl apply -f triggers/ -n testkubeWhy teams choose Testkube Git Triggers
Security & compliance
- No inbound webhook endpoints to secure
- Tests run inside your own infrastructure. Data never leaves your network
- Kubernetes RBAC and network policies apply naturally
- Full audit trail in the Testkube Dashboard
Vendor independence
- Works with every Git provider, switch vendors without rewriting pipelines
- No marketplace dependency, use any container image as a test runner
- Portable across clouds, on-premises, and hybrid environments
Air-gapped ready
- Polling-based architecture requires only outbound access to your Git server
- Pure Go Git implementation, no external binaries
- All components run as Kubernetes workloads using your private container registry
Purpose-built for testing
- Native support for test frameworks (k6, Playwright, Cypress, JMeter, etc.)
- Built-in artifact collection and test analytics
- Parallel test execution across Kubernetes nodes
- Flaky test detection and historical trend analysis
Cost efficiency
- No per-minute runner billing, use your existing Kubernetes capacity
- Shared cluster resources across all test workloads
- Scale test parallelism with Kubernetes auto-scaling
Summary
Testkube Git Triggers provide a vendor-agnostic, air-gap-compatible, Kubernetes-native alternative to GitHub Actions for test automation. By using a polling-based architecture instead of webhooks, Testkube eliminates the dependency on any single Git vendor and enables test automation in environments where GitHub Actions simply cannot operate.
For teams deeply invested in GitHub, the git-pull-request event offers the best of both worlds: continue using GitHub for code hosting, pull requests, and collaboration while running your test workloads inside your own Kubernetes cluster, with access to real services, real databases, and production-like network conditions.
Whether you're running on GitHub, GitLab, Bitbucket, Azure DevOps, or a self-hosted Gitea instance behind a firewall, Testkube Git Triggers give you the same powerful, event-driven test automation with the added benefits of running tests inside your actual infrastructure.
Ready to get started? Visit testkube.io to learn more, or check out the Testkube documentation for detailed setup instructions.
About Testkube
Testkube is the open testing platform for AI-driven engineering teams. It runs tests directly in your Kubernetes clusters, works with any CI/CD system, and supports every testing tool your team uses. By removing CI/CD bottlenecks, Testkube helps teams ship faster with confidence.
Get Started with a trial to see Testkube in action.





