A modern, feature-rich Concourse CI resource for GitHub Pull Requests with dual-mode support and GitHub App authentication.
- PR List Mode: Track all PRs matching criteria (perfect for instance pipelines)
- Single PR Mode: Track commits to a specific PR (ideal for commit-based testing)
- GitHub App Authentication (recommended) - No shared tokens, automatic token rotation
- Personal Access Token support
- Required review approvals enforcement
- Draft PR filtering
- Fork repository filtering
- Path-based filtering with glob patterns (include/exclude)
- Label-based filtering for workflow control
- Base branch targeting
- PR state filtering (OPEN, MERGED, CLOSED)
- Multiple integration strategies (merge, rebase, checkout)
- Commit status updates
- PR comments
- Changed files detection
- Shallow cloning support
- Git submodules support
- Git LFS support
- Configurable git depth
- Multi-architecture support (amd64, arm64)
- PR list mode checks up to 10 PRs concurrently by default, configurable via
check_concurrency(path filtering, comment scanning) instead of one at a time β see Concurrency
resource_types:
- name: github-pr
type: registry-image
source:
repository: ghcr.io/ujala-singh/github-pr-concourse-resource
tag: latest
# Or use a specific version tag:
# tag: v1.0.0resource_types:
- name: github-pr
type: registry-image
source:
repository: jolly3/github-pr-concourse-resource
tag: latestRequirements:
- Go 1.27 or higher
- Docker (for containerization)
# Clone the repository
git clone https://github.com/ujala-singh/github-pr-concourse-resource.git
cd github-pr-concourse-resource
# Build Docker image
docker build -t github-pr-concourse-resource:latest .
# Run tests
go test -v -race -cover ./...
# Push to your registry
docker tag github-pr-concourse-resource:latest your-registry.com/github-pr-concourse-resource:latest
docker push your-registry.com/github-pr-concourse-resource:latest- Concourse CI 7.0 or higher
- GitHub repository with appropriate permissions
- One of the following authentication methods:
- GitHub App (recommended) - Setup Guide
- Personal Access Token with
reposcope
See our Quick Start Guide for a step-by-step tutorial.
π Looking for more examples?
- Complete Pipeline Template - Ready-to-use pipeline with all features
- Real-World Examples - 13 detailed scenarios with best practices
resource_types:
- name: github-pr
type: registry-image
source:
repository: ghcr.io/ujala-singh/github-pr-concourse-resource
resources:
- name: pull-requests
type: github-pr
source:
repository: owner/repo
access_token: ((github-token))
jobs:
- name: test-pr
plan:
- get: pull-requests
trigger: true
- task: run-tests
config:
platform: linux
image_resource:
type: registry-image
source: {repository: golang, tag: "1.27"}
inputs:
- name: pull-requests
run:
path: sh
args:
- -c
- |
cd pull-requests
go test ./...Track a list of PRs matching your criteria. Perfect for creating instance pipelines where each PR gets its own pipeline.
When to use: You want to create separate pipelines for each PR, or you want to trigger on the creation/closure of PRs rather than individual commits.
resources:
- name: prs
type: github-pr
source:
repository: owner/repo
access_token: ((github-token))
# No 'number' field = PR List mode
base_branch: main
labels: ["ready-for-review"]
required_review_approvals: 1Track commits to a specific PR number. Perfect for running tests on each commit to a PR.
When to use: You have a known PR number and want to run tests on every commit to that PR.
resources:
- name: pr-123
type: github-pr
source:
repository: owner/repo
access_token: ((github-token))
number: 123 # Specific PR number = Single PR modeYou can authenticate with either a personal access token or GitHub App credentials (recommended).
source:
repository: owner/repo
access_token: ((github-token))source:
repository: owner/repo
github_app_id: ((github-app-id))
github_app_installation_id: ((github-app-installation-id))
github_app_private_key: ((github-app-private-key))See docs/GITHUB_APP_AUTHENTICATION.md for detailed setup instructions.
| Parameter | Required | Default | Description |
|---|---|---|---|
repository |
Yes | - | Repository in owner/repo format |
access_token |
No* | - | GitHub personal access token with repo scope |
github_app_id |
No* | - | GitHub App ID |
github_app_installation_id |
No* | - | GitHub App Installation ID |
github_app_private_key |
No* | - | GitHub App private key. Accepts either a raw PEM string or that same PEM base64-encoded β useful when storing it in a secrets manager or credential store that mangles multi-line values. Validated at config load, so a malformed key fails fast with a clear error instead of surfacing deep inside JWT signing. |
v3_endpoint |
No | https://api.github.com |
GitHub API v3 endpoint (for GitHub Enterprise) |
v4_endpoint |
No | https://api.github.com/graphql |
GitHub API v4 endpoint (for GitHub Enterprise) |
hosting_endpoint |
No | https://github.com |
GitHub hosting endpoint (for GitHub Enterprise) |
paths |
No | [] |
Only trigger on PRs that change files matching these patterns. Checked against files changed since the last build for an already-tracked PR, or the PR's full base...HEAD diff when there's no previous build to diff from (e.g. a PR appearing for the first time) β see Path Filtering and Diff Scope. |
ignore_paths |
No | [] |
Ignore PRs that only change files matching these patterns. Same diff scope as paths above. |
disable_ci_skip |
No | false |
If false, PRs with [ci skip] or [skip ci] in title are skipped |
skip_ssl_verification |
No | false |
Skip SSL certificate verification (use with caution!) |
disable_forks |
No | false |
Only trigger on PRs from the same repository |
ignore_drafts |
No | false |
Skip draft PRs |
base_branch |
No | - | Only trigger on PRs targeting this branch |
labels |
No | [] |
Only trigger on PRs with at least one of these labels |
states |
No | ["OPEN"] |
PR states to track: OPEN, MERGED, CLOSED, DRAFT. DRAFT is a pseudo-state β GitHub's real PR state is only ever OPEN/MERGED/CLOSED, and draft-ness is a separate flag on an open PR β so DRAFT fetches open PRs and keeps only the draft ones. states: ["OPEN"] (the default) already includes drafts, same as always; adding "DRAFT" alongside "OPEN" is a no-op β you only need "DRAFT" on its own (optionally with MERGED/CLOSED) when you want only drafts, not every open PR. |
concourse_url |
No | - | Override the public URL used for PR status check "Details" links. Useful when Concourse's externalUrl Helm value is locked to a different hostname (e.g. an STS WebIdentity OIDC issuer) but builds are reached via a proxy or alternate domain. When set, this value replaces ATC_EXTERNAL_URL for the put step. |
trigger_comments |
No | [] |
Comment prefixes (case-insensitive) that re-trigger the job as if a new commit had arrived, without pushing one. E.g. ["concourse plan"] lets a PR comment of "concourse plan" (or "concourse plan --all", etc.) re-run the job against the PR's current HEAD. See Comment Triggers below. |
check_concurrency |
No | 10 |
PR list mode only (ignored in single-PR mode). How many PRs to inspect in parallel per check (path filtering, comment scanning). Must be between 1 and 50 if set. See Concurrency below. |
Note: Either
access_tokenOR all three GitHub App parameters (github_app_id,github_app_installation_id,github_app_private_key) must be provided.
| Parameter | Required | Default | Description |
|---|---|---|---|
required_review_approvals |
No | 0 |
Minimum number of approved reviews required |
comment_note_key |
No | derived from source |
Names this resource's marker on the comment-trigger note. Allowed characters: letters, digits, _, ., -. Set it only to tell apart two list-mode resources whose source settings are otherwise identical. |
| Parameter | Required | Default | Description |
|---|---|---|---|
number |
Yes | - | The PR number to track |
required_review_approvals |
No | 0 |
Minimum number of approved reviews required |
git_crypt_key |
No | - | Base64 encoded git-crypt key for encrypted repositories |
disable_git_lfs |
No | false |
Disable Git LFS |
Set trigger_comments to re-run a job from a PR comment instead of a new commit β handy for re-running a plan after an unrelated failure, without pushing an empty commit:
source:
repository: owner/repo
access_token: ((github-token))
trigger_comments:
- "concourse plan"Commenting concourse plan (case-insensitive, prefix match) on the PR causes the next check to emit a new version for that PR's current HEAD β not the commit that existed when the comment was posted. This works the same way in both modes:
-
Single PR mode (
numberset): tracks that one PR's comment watermark directly, on the resource's own version. No extra setup needed. -
PR list mode (
numberunset): tracks every PR's comment watermark independently via a shared table (a PR number -> comment ID map) that survives regardless of which real PR's version Concourse happens to remember as "latest" in between checks. A real PR's owncomment_id/comment_baselinestill describes only that PR β nothing about one PR's version ever depends on another's β so a PR closing, or getting a new comment, never perturbs any other PR's version.The table only needs recording when it actually changes, and whenever it does, it's always recorded on a dedicated bookkeeping version β a reserved version with
pr: "0", which never corresponds to a real PR β never on a real PR's own version. An earlier design tried attaching the table to whichever real PR's version was already new that cycle for its own reason (a real comment trigger), to skip the extra bookkeeping build β but that's unsafe: that PR's version then toggles between carrying the table and not, cycle to cycle, purely based on which PR happens to be picked as the carrier, even though nothing about that PR itself changed. Concourse diffs the full version, so that alone makes an already-built, unchanged PR look like a new version and rebuilds it.This means
pr-plan-style jobs in PR list mode must recognize and skip the bookkeeping version when one does show up. The resource'sinstep writes aCOMMENT_TRIGGER_BOOKKEEPINGmarker file at the root of the fetched directory instead of checking out a repository for it (there is no repository to check out β it's not a real PR). Add an early exit to your task script:if [ -f pull-request/COMMENT_TRIGGER_BOOKKEEPING ]; then echo "bookkeeping version, nothing to plan" exit 0 fi
You'll see one extra, fast no-op build in the Concourse UI any time the table changes β not just when a PR closes/merges with nothing else happening. That's the deliberate, bounded cost of never risking a spurious rebuild of a real, unchanged PR.
Do not use
try:to make that no-op build show green. It's tempting, buttry:masks the failure of its entire wrapped scope with success β wrapping a realput(e.g. to atofu/terraform resource) intry:to "skip it gracefully" would also silently mask genuine plan/apply failures as green. Useload_var+acrossinstead: have a task always succeed and write an empty list (or a single-element list) to a file based on the marker,load_varit, then wrap the real work inacross: { values: ((.:the_var)) }β the real work then runs zero times (a trivial, immediate success) for the bookkeeping version, or once for a real PR, with no risk of masking a real failure. See concourse/concourse#7521 for whytry:is unsafe here.The comment-trigger note (PR list mode). Concourse hands
checkonly the resource's current version, so the bookkeeping version's table is only readable while it's the current version β and right after a comment triggers, the current version is that PR's, not the bookkeeping version. To cover that gap, each PR also carries its own record on GitHub that every check can read. When a build'sgetpicks up a trigger comment, it posts (or edits) a single comment on that PR β shared by every pipeline using the same GitHub credentials β with one row per resource:Job Picked up infra-aws / pr #9 A concourse plancomment by someoneinfra-gcp / pr #4 A concourse plancomment by someoneComment again to run them again. This comment updates in place.
Each resource's row shows the latest build whose
getpicked up a trigger, and an older comment never takes it back. Hidden in the note, each resource also keeps a signed marker recording the newest trigger comment it has handled. A check that still has the bookkeeping table uses the higher of the table and its marker; a check without the table uses the marker; a resource with no marker on the PR has handled nothing, so the PR's newest trigger comment fires β provided it was posted after the PR's head commit (an older one was already superseded by that push). That's what makes the firstconcourse planon a PR fire even when some other PR's version is currently "latest".Key properties:
- A marker per resource, keyed by
source(repository, paths, filters, trigger patterns β never credentials), so one pipeline handling a comment never stops another from triggering on it. Setcomment_note_keyto tell apart two resources whose settings are otherwise identical; changing those settings starts a new marker. - Concurrency-safe. One comment triggers every pipeline at once, so their
gets write the note together. Each waits a short random moment, re-reads after writing, and re-checks a few seconds later, rewriting anything another job's edit dropped; duplicate notes (two created at once) are folded into the oldest. A dropped marker is harmless β the resource's next table-less check re-fires the same version Concourse already built, in the same place, so nothing rebuilds; only its row is missing until its next trigger. - Forgery-resistant. The marker is signed with a key derived from the resource's credentials, so a task can't forge it, and only comments posted by the resource's own GitHub App (
github_app_id) oraccess_tokenuser are read at all. The note never copies a trigger comment's text β it names the pattern that matched and the author without mentioning them, and rows are re-rendered from stored data (markup, HTML, @-mentions and non-plain links stripped) on every write β so a hand-edit to its text is never re-posted. delete_previous_commentskeeps it.outdeletes the resource's earlier comments except the note, and the note is size-bounded (dropping the oldest entries first β rows and markers alike, never the writing resource's own) to stay under GitHub's comment limit.getfails if it can't write the note, naming the permission it needs (Pull requests: write) rather than silently leaving the comment looking new to every table-less check. When first rolled out, a PR that already has a trigger comment but no marker may run once.
- A marker per resource, keyed by
The very first trigger_comments-eligible check for a PR (or the first check after upgrading to a version of this resource that supports it) only establishes the watermark β it won't retroactively fire on comments that already existed before the pipeline started watching for them.
Comment triggers are independent of paths/ignore_paths matching on the latest push. Once a PR has an established comment-trigger watermark, a matching comment fires regardless of whether the PR's most recent commit happens to touch a configured path β a comment isn't a push, and there's no reason a manual "please re-plan" request should depend on what the latest commit touched. (An earlier version of this resource got this wrong: once path filtering was scoped to the diff since the last build instead of the PR's full history, a PR whose latest commit didn't touch a matching path could permanently stop being eligible for comment triggers too, since nothing else ever advances a stale cursor forward. Fixed β comment-trigger eligibility for the resource's tracked PR no longer depends on the commit-triggering path match.)
PR List Mode: Returns the current version of every PR matching the filter criteria, on every check. A new version is picked up by Concourse when:
- A new PR is opened
- An existing, already-tracked PR gets a new commit
- A PR is closed/merged
- A PR's state changes (e.g., from draft to ready)
- (if
trigger_commentsis set) a new matching comment is posted β see Comment Triggers
Single PR Mode: Returns list of commits to the specified PR. A new version is emitted for each new commit, or (if trigger_comments is set) for a matching comment.
Every version this resource returns carries extra fields you'll see in Concourse's resource-version listing whenever
trigger_commentsis configured:comment_idandcomment_baseline. These are an internal watermark, not a log of every comment ever posted βcomment_idis just the highest matching-comment ID seen so far for that version's own PR, andcomment_baselinemarks that the watermark has been established at least once. They show up on ordinary commit-triggered versions too (not just comment-triggered ones) because the resource needs this watermark carried forward on every version to correctly detect the next new comment on that same PR. If you don't settrigger_comments, neither field appears. In PR list mode, you'll additionally see one dedicated version per check withpr: "0"and acomment_watermarksfield (a JSON object mapping every tracked PR number to its own watermark) β see Comment Triggers for why, and what your task script needs to do about it.
paths/ignore_paths are checked against the files changed since the last build for a PR this resource already has a previous commit recorded for β not against the PR's entire history. This matters because GitHub's "list files changed in a PR" API always returns the full base-branch-to-HEAD diff, no matter which commit you ask about: without scoping to a specific commit range, a PR that touched a matching path in any commit would keep matching forever, even once its latest push only changed unrelated files, causing redundant/surprising re-triggers.
- Single PR Mode: fully correct. This mode always has a reliable previous commit (the one it last built) to diff from.
- PR List Mode: correct for whichever PR currently is the resource's single tracked cursor (see Comment Triggers for the same cursor concept), if it's been pushed to since it was last built. For every other PR β one appearing for the first time, an unchanged one, or one the cursor has moved away from β there's no previous commit to diff from, so the check falls back to the PR's full base...HEAD diff, same as before this existed. In practice this means: a PR you're actively pushing to gets precise, per-push path filtering once it's been checked at least once; a PR nobody's touched recently is evaluated on its full history, same as always.
PR List Mode only. A single check does one GitHub API call per PR per pass β one to test paths/ignore_paths, and (if trigger_comments is set) another to scan that PR's comments. With many open PRs on the repo, running these sequentially can make one check take tens of seconds and eat into the GitHub App's rate limit.
To reduce that, both passes run with up to check_concurrency PRs in flight concurrently β 10 by default, configurable up to 50 (see the check_concurrency source field above). This cuts wall-clock time per check roughly by that factor on repos with many matching PRs; it does not reduce the total number of GitHub API calls made, so it doesn't help with rate-limit budget β only with how long a single check takes to run. Raising it further mostly just shifts where the bottleneck is, into GitHub's own per-token concurrency/secondary rate limits. Output order is unaffected: results are always assembled back into the same order GitHub returned the PRs in, regardless of which concurrent call finishes first.
Single PR Mode does not need this β it tracks exactly one PR, so there's nothing to parallelize.
PR List Mode: Fetches PR metadata without cloning the repository.
Parameters:
| Parameter | Default | Description |
|---|---|---|
skip_download |
false |
Only fetch metadata, don't create files |
Single PR Mode: Clones the repository and integrates the PR.
Parameters:
| Parameter | Default | Description |
|---|---|---|
skip_download |
false |
Skip cloning (for status updates only) |
integration_tool |
merge |
How to integrate: merge, rebase, or checkout |
git_depth |
0 |
Shallow clone depth (0 = full clone) |
submodules |
false |
Recursively clone submodules |
list_changed_files |
false |
Create list of changed files |
fetch_tags |
false |
Fetch tags from remote |
Updates PR status and adds comments. Available in both modes β cmd/out always runs the same underlying implementation regardless of number being set, using whatever PR was checked out by the preceding get step.
Parameters:
| Parameter | Required | Description |
|---|---|---|
path |
Yes | Path to the PR resource from get |
status |
No | Commit status: success, failure, error, pending |
base_context |
No | Status context prefix (default: concourse-ci) |
context |
No | Status context suffix (default: status) |
target_url |
No | URL to link from the status |
description |
No | Status description |
description_file |
No | File containing the status description |
comment |
No | Comment text to add to PR |
comment_file |
No | File containing comment text |
delete_previous_comments |
No | Delete this resource's earlier comments on the PR before adding a new one |
put acts only on the exact version the preceding get fetched, which get signs (.git/resource/version.sig) with a key derived from the resource's credentials. A checkout whose signature is missing, doesn't verify, or was changed after the get fails the put before it reports anything, and the pr/head_sha files are ignored. put returns that version unchanged, so its own output never looks like a new version that would re-trigger the job. In single PR mode the version must also be for source.number. (The signature covers the version, its PR and commit, and the repository β not the build β since Concourse reuses a cached get across builds.)
The resource provides metadata in multiple formats:
-
Individual files in
.git/resource/:pr- PR numberurl- PR URLtitle- PR titleauthor- Author usernamehead_ref- Head branch namehead_sha- The commit that was checked out β the version's commit, even if the PR has been pushed to sincebase_ref- Base branch namebase_sha- Base branch tip the PR was integrated onto, captured before the PR is checked out (Single PR mode only). Diff$(git merge-base base_sha head_sha)..head_shafor the PR's own changesstate- PR stateapproved_review_count- Number of approvals
-
JSON files in
.git/resource/:version.json- Version objectmetadata.json- Full metadata array
-
Changed files (if
list_changed_files: true):.git/resource/changed_files- Newline-separated list
resources:
- name: prs
type: github-pr
source:
repository: myorg/myrepo
access_token: ((github-token))
base_branch: main
ignore_drafts: true
required_review_approvals: 2
labels: ["ready-for-ci"]
jobs:
- name: test-pr
plan:
- get: prs
trigger: true
version: every
- task: run-tests
config:
platform: linux
image_resource:
type: registry-image
source: {repository: golang}
inputs:
- name: prs
run:
path: sh
args:
- -c
- |
PR_NUMBER=$(cat prs/.git/resource/pr)
echo "Testing PR #$PR_NUMBER"
# Your test commands hereresources:
- name: my-pr
type: github-pr
source:
repository: myorg/myrepo
access_token: ((github-token))
number: 42
jobs:
- name: test
plan:
- get: my-pr
trigger: true
- put: my-pr
params:
path: my-pr
status: pending
context: tests
- task: run-tests
config:
platform: linux
image_resource:
type: registry-image
source: {repository: golang}
inputs:
- name: my-pr
run:
path: sh
args: ["-c", "cd my-pr && go test ./..."]
on_success:
put: my-pr
params:
path: my-pr
status: success
context: tests
on_failure:
put: my-pr
params:
path: my-pr
status: failure
context: tests
comment: "Tests failed! Please check the build."resources:
- name: backend-prs
type: github-pr
source:
repository: myorg/myrepo
access_token: ((github-token))
paths:
- "backend/**"
- "api/**"
ignore_paths:
- "**/*.md"
- "docs/**"resources:
- name: pr
type: github-pr
source:
repository: myorg/myrepo
access_token: ((github-token))
number: 42
jobs:
- name: test-with-rebase
plan:
- get: pr
trigger: true
params:
integration_tool: rebase # Rebase PR on top of base
list_changed_files: trueRequirements:
- Go 1.27 or higher
- Docker 24.0 or higher
- Git 2.40 or higher
# Clone repository
git clone https://github.com/ujala-singh/github-pr-concourse-resource.git
cd github-pr-concourse-resource
# Install dependencies
go mod download
# Build binaries
go build -o bin/check ./cmd/check
go build -o bin/in ./cmd/in
go build -o bin/out ./cmd/out
# Build Docker image
docker build -t github-pr-concourse-resource:dev .
# Run locally (for testing)
docker run -i github-pr-concourse-resource:dev /opt/resource/check < check-request.json# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run with race detection
go test -race ./...
# Run with coverage
go test -cover ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Run specific test
go test ./models -run TestGetAccessToken
# Run tests in watch mode (requires entr)
find . -name "*.go" | entr -c go test ./...# Format code
go fmt ./...
# Run linter (requires golangci-lint)
golangci-lint run
# Vet code
go vet ./...
# Static analysis
staticcheck ./....
βββ cmd/
β βββ check/ # Check for new versions
β βββ in/ # Fetch resource
β βββ out/ # Update resource (status/comments)
βββ models/ # Core data models and GitHub client
β βββ github_app.go # GitHub App authentication
β βββ models.go # Data structures
βββ pr/ # Single PR mode implementation
βββ prlist/ # PR List mode implementation
βββ docs/ # Comprehensive documentation
β βββ QUICKSTART.md
β βββ ARCHITECTURE.md
β βββ IMPLEMENTATION.md
β βββ GITHUB_APP_AUTHENTICATION.md
βββ Dockerfile # Multi-stage build configuration
Symptom: HTTP 401 or Bad credentials errors
Solutions:
- Verify your access token has
reposcope - For GitHub App: Ensure all three parameters are correct
- Check token hasn't expired
- Verify repository access permissions
# Debug: Enable verbose logging
source:
repository: owner/repo
access_token: ((github-token))
# Test token with: curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/userSymptom: PRs created but pipeline doesn't trigger
Solutions:
- Check filter criteria (labels, base_branch, etc.)
- Verify webhook configuration (if using webhooks)
- Check PR meets required_review_approvals
- Look at draft status if
ignore_drafts: true
# Debug check behavior
fly -t your-target check-resource --resource pipeline/pr-resourceSymptom: fatal: could not read Username or authentication errors during clone
Solutions:
- Ensure GitHub App installation has repository access
- Verify private key format (must be PEM with newlines preserved)
- Check network connectivity to GitHub
- For private repos, confirm authentication method has access
Symptom: merge conflict during get step
Solutions:
- Use
integration_tool: rebaseinstead ofmerge - Use
integration_tool: checkoutto skip integration - Address conflicts in the PR before running pipeline
Enable detailed logging by setting environment variables:
jobs:
- name: debug-pr
plan:
- get: pull-requests
params:
skip_download: false
# Add environment for debugging
env:
- name: DEBUG
value: "true"- π Check documentation
- π Report issues
- π¬ Start a discussion
- π§ Contact maintainers
- Shallow cloning: Set
git_depthto reduce clone time - Path filtering: Use
pathsandignore_pathsto reduce check frequency - PR List Mode: Scales to hundreds of PRs efficiently
- Multi-architecture: Native ARM64 support for Apple Silicon and ARM servers
- Use GitHub Apps instead of personal access tokens
- Limit token scopes to minimum required (
repofor PAT) - Store secrets in Concourse credential managers (Vault, etc.)
- Enable required reviews with
required_review_approvals - Filter forks with
disable_forks: truefor security-sensitive repos - Use signed commits when possible
- Regular updates: Keep the resource image updated
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add/update tests
- Ensure tests pass (
go test ./...) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See the Development section above for setup instructions.
MIT License - see LICENSE file for details.
Special thanks to all contributors and the Concourse CI community!
- Real-World Examples - 13 detailed examples for common scenarios
- Complete Pipeline Template - Ready-to-use pipeline with all features
- Troubleshooting Guide - Solutions for common issues
- Architecture Overview
- Implementation Details
- GitHub App Setup
- Quick Start Guide
Maintained with β€οΈ by the community | Report Issues | Request Features