Skip to main content

Overview

Pipelines-as-Code provides concurrency control to limit the number of PipelineRuns that can execute simultaneously for a repository. This prevents resource exhaustion when multiple events trigger pipelines at the same time.

Configuration

Repository-Level Concurrency Limit

Set concurrency_limit in the Repository CR to define the maximum number of concurrent PipelineRuns:
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: my-repo
  namespace: my-namespace
spec:
  url: "https://github.com/owner/repo"
  concurrency_limit: 3
concurrency_limit
integer
Maximum number of PipelineRuns that can run concurrently for this repository.
  • Minimum value: 1
  • Default: No limit (all matching PipelineRuns start immediately)
  • Applies to: All PipelineRuns triggered by events in this repository

How It Works

When a Git event triggers multiple PipelineRuns:
  1. Without concurrency limit: All PipelineRuns start immediately
  2. With concurrency limit:
    • Up to concurrency_limit PipelineRuns start immediately
    • Remaining PipelineRuns are queued
    • Queued PipelineRuns start as running ones complete
    • PipelineRuns execute in alphabetical order by name

Execution Order

PipelineRuns are started in alphabetical order:
# .tekton directory contains:
# - a-unit-tests.yaml
# - b-integration-tests.yaml  
# - c-e2e-tests.yaml
# - d-security-scan.yaml
With concurrency_limit: 2:
  1. a-unit-tests and b-integration-tests start immediately
  2. c-e2e-tests and d-security-scan are queued
  3. When a-unit-tests completes, c-e2e-tests starts
  4. When b-integration-tests completes, d-security-scan starts
Use alphabetical naming (e.g., 01-, 02-, etc.) to control execution order when using concurrency limits.

Use Cases

apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: resource-heavy-builds
spec:
  url: "https://github.com/myorg/heavy-app"
  # Each build uses 8GB memory, cluster has 32GB total
  concurrency_limit: 3
Prevents out-of-memory errors by limiting concurrent memory-intensive builds.
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: cloud-native-app
spec:
  url: "https://github.com/myorg/app"
  # Limit concurrent cloud resource usage
  concurrency_limit: 2
Reduces cloud costs by limiting the number of expensive resources (VMs, build machines) running simultaneously.
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: monorepo
spec:
  url: "https://github.com/myorg/monorepo"
  # Ensure critical pipelines run first
  concurrency_limit: 1
With naming like:
  • 01-lint.yaml
  • 02-unit-tests.yaml
  • 03-integration-tests.yaml
  • 04-build.yaml
Ensures strict sequential execution.
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: api-integration
spec:
  url: "https://github.com/myorg/api-client"
  # External API limits to 3 concurrent requests
  concurrency_limit: 3
Prevents overwhelming external services with too many concurrent requests.

Example: Pull Request with Multiple Pipelines

Given this .tekton directory:
.tekton/
├── lint.yaml
├── unit-tests.yaml
└── integration-tests.yaml
Each file contains a PipelineRun matching on pull_request events. Without concurrency limit:
spec:
  url: "https://github.com/owner/repo"
When a PR is opened: All 3 PipelineRuns start immediately. With concurrency limit:
spec:
  url: "https://github.com/owner/repo"
  concurrency_limit: 1
When a PR is opened:
  1. integration-tests starts (alphabetically first)
  2. lint is queued
  3. unit-tests is queued
  4. When integration-tests completes, lint starts
  5. When lint completes, unit-tests starts

Queue Behavior

Queued PipelineRun States

  • Running: Currently executing (up to concurrency_limit)
  • Pending: Queued, waiting for a slot (shown with Pending status)
  • Unknown: May appear briefly during state transitions

Viewing Queue Status

# List all PipelineRuns for a repository
kubectl get pipelineruns -n my-namespace -l pipelinesascode.tekton.dev/repository=my-repo

NAME                          STATUS      STARTTIME   COMPLETIONTIME
lint-pr-123-abc               Running     2m ago
unit-tests-pr-123-def         Pending     2m ago
integration-tests-pr-123-ghi  Pending     2m ago

Queue Skipping

PAC skips PipelineRuns in these states when checking the queue:
  • Running: Actively executing
  • Pending: Waiting in queue
PAC does not skip PipelineRuns with Unknown status, which may cause them to be counted against the concurrency limit until their state is resolved.

Global vs Repository Configuration

Concurrency can be configured at two levels:
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: my-repo
spec:
  url: "https://github.com/owner/repo"
  concurrency_limit: 2
Applies only to this specific repository.

Global Level (via ConfigMap)

Configure a default for all repositories:
apiVersion: v1
kind: ConfigMap
metadata:
  name: pipelines-as-code
  namespace: pipelines-as-code
data:
  default-concurrency-limit: "5"
Repository-level settings override global defaults.

Interaction with Other Features

Concurrency + cancel-in-progress

Concurrency limits cannot be used together with the cancel-in-progress annotation.
This annotation automatically cancels in-progress runs when a new commit is pushed:
metadata:
  annotations:
    pipelinesascode.tekton.dev/cancel-in-progress: "true"
If you enable both:
  • cancel-in-progress takes precedence
  • concurrency_limit is ignored
  • You’ll see a warning in the PAC controller logs
See Running PipelineRuns for more details.

Concurrency + max-keep-runs

These features work together:
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: my-repo
spec:
  url: "https://github.com/owner/repo"
  concurrency_limit: 3
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: build-pipeline
  annotations:
    pipelinesascode.tekton.dev/max-keep-runs: "10"
spec:
  # ...
  • concurrency_limit: Controls how many run simultaneously
  • max-keep-runs: Controls how many completed runs are retained
See Cleanup for retention policies.

Kubernetes-Native Queueing with Kueue

For more advanced queueing scenarios, PAC integrates with Kueue:
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: my-repo
  annotations:
    kueue.x-k8s.io/queue-name: user-queue
spec:
  url: "https://github.com/owner/repo"
Kueue provides:
  • Fair-sharing between multiple repositories
  • Priority-based scheduling
  • Resource quotas and preemption
  • Integration with cluster autoscaling
Experimental: Kueue integration via konflux-ci/tekton-kueue is for testing only and not recommended for production.

Troubleshooting

Possible causes:
  • Another PipelineRun is stuck in Running state
  • Concurrency limit set too low
  • Cluster resource constraints preventing execution
Debug:
# Check currently running PipelineRuns
kubectl get pr -n my-namespace -l pipelinesascode.tekton.dev/repository=my-repo --field-selector=status.conditions[0].type=Succeeded,status.conditions[0].status=Unknown

# Check for resource constraints
kubectl describe pr <stuck-pipelinerun>
Cause: PipelineRuns are executed in alphabetical order by nameSolution: Use prefixes to control order:
.tekton/
├── 01-lint.yaml
├── 02-test.yaml
└── 03-build.yaml
Possible causes:
  • concurrency_limit not set in Repository CR
  • Typo in field name (check YAML)
  • Repository CR not in correct namespace
  • Using cancel-in-progress annotation
Verify:
kubectl get repository my-repo -n my-namespace -o jsonpath='{.spec.concurrency_limit}'
Cause: Concurrency limit applies per Repository, not globallyExample: 3 repositories each with concurrency_limit: 5 = up to 15 total concurrent runsSolution:
  • Reduce per-repository limits
  • Use global ConfigMap default
  • Implement Kueue for cluster-wide quotas

Best Practices

  1. Start Conservative: Begin with low limits and increase based on monitoring
  2. Monitor Resource Usage: Track CPU, memory, and storage to determine optimal limits
  3. Name for Order: Use numerical prefixes when execution order matters:
    01-lint
    02-unit-test
    03-integration-test
    
  4. Consider Dependencies: If pipelines depend on each other, use concurrency_limit: 1
  5. Different Limits per Repo: Heavy builds might need limit: 1, light tests can use limit: 5
  6. Document Limits: Add comments explaining why specific limits were chosen
  7. Test Queue Behavior: Manually trigger multiple pipelines to verify queueing works as expected
  8. Alert on Long Queues: Monitor Pending PipelineRuns and alert if queues grow too large

Performance Considerations

Pros:
  • Fastest feedback for developers
  • No waiting in queue
Cons:
  • May overwhelm cluster resources
  • Higher cloud costs
  • Risk of resource exhaustion
Best for: Small pipelines, large clusters, unlimited cloud resources
Pros:
  • Balanced resource usage
  • Predictable costs
  • Still provides reasonable feedback time
Cons:
  • Some queueing during high activity
  • Requires capacity planning
Best for: Most production scenarios
Pros:
  • Minimal resource usage
  • Strict cost control
  • Ensures execution order
Cons:
  • Longest wait times
  • Can bottleneck development
Best for: Resource-constrained environments, sequential dependencies

Example Configurations

apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: staged-pipeline
spec:
  url: "https://github.com/myorg/app"
  # Ensure stages run in order
  concurrency_limit: 1
# .tekton/ directory structure
01-validate.yaml    # Quick validation
02-build.yaml       # Build artifacts
03-test.yaml        # Run tests against build
04-deploy.yaml      # Deploy to staging
# Team A - Heavy builds
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: team-a-service
spec:
  url: "https://github.com/myorg/monorepo"
  concurrency_limit: 2
---
# Team B - Lightweight tests  
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: team-b-service
spec:
  url: "https://github.com/myorg/monorepo"
  concurrency_limit: 5
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
  name: cloud-builds
spec:
  url: "https://github.com/myorg/app"
  # Each build spins up expensive cloud VMs
  concurrency_limit: 1
# PipelineRun with expensive resources
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: cloud-build
spec:
  taskRunTemplate:
    podTemplate:
      nodeSelector:
        cloud.google.com/gke-nodepool: expensive-pool
  # ...

See Also

Build docs developers (and LLMs) love