Tekton PAC CI Guide — Part I. Setup
by bless2k@gmail.com(AI-assisted)
Tekton은 OpenShift Cluster를 지원하는 CI tool이다. Tekton Pipelines-as-Code(PAC)는 GitHub Actions처럼 .tekton/*.yaml을 리포지토리에 커밋해두면 PR/push 이벤트에 반응해 파이프라인이 자동 실행된다는 점은 유사하지만, 실행 주체가 GitHub의 매니지드 러너가 아니라 사용자 자신의 Kubernetes 클러스터 위 Tekton controller이다.
CI 워크플로 자체는 YAML이 아니라 Kubernetes CRD — PipelineRun/Task로 정의되고, GitHub App 설치·웹훅·Repository CR 등록·클러스터 Ingress 노출 같은 인프라 구축이 선행되어야 하며(Actions는 리포지토리에 파일만 넣으면 끝), 시크릿도 GitHub Secrets가 아니라 클러스터의 Kubernetes Secret으로 직접 관리한다. 대신 /retest, /test 같은 GitOps 코멘트 명령, 클러스터 내부 레지스트리·네트워크에 대한 직접 접근, on-prem/에어갭 환경 지원, Task 재사용 등이 가능하다.
전반적으로 설정이 GitHub Actions CI 설정보다 어렵지만 좀 더 세밀한 제어가 가능하다.
Prerequisites
- A Kubernetes/k3s cluster, cluster-admin privileges on it, and kubectl pointed at it. Tekton Pipelines itself, plus the tkn/tkn-pac CLIs, are installed in Step 1 below — no need to have them already.
- gh CLI, authenticated (gh auth login, then gh auth status to confirm — a stale/invalid token is a common failure mode, see Gotcha #0 below).
- A domain/Ingress (or gosmee) so GitHub can reach the cluster.
Two empty GitHub repos: app-repo and deployment-repo.
Step 1 — Install Tekton Pipelines and PAC
Part of the Tekton Pipelines-as-Code CI/CD Example implementation guide. This step gets the cluster ready: Tekton Pipelines itself, the tkn and tkn-pac CLIs, and the Pipelines-as-Code (PAC) controller/webhook/watcher — all installed and healthy before anything App- or repo-specific begins.
Each verification below follows Command → Expected output → If it fails. A command running without error is not the same as the step being done — confirm the output actually matches before checking the box.
1a — Tekton Pipelines (PAC’s own prerequisite)
Check first — don’t reinstall if it’s already there:
kubectl apply --filename https://infra.tekton.dev/tekton-releases/pipeline/previous/v1.15.0/release.yaml
kubectl -n tekton-pipelines wait --for=condition=Available --timeout=180s deployment --allVerify:
$ $ kubectl get deployment -n tekton-pipelines
NAME READY UP-TO-DATE AVAILABLE AGE
tekton-events-controller 1/1 1 1 4d10h
tekton-pipelines-controller 1/1 1 1 4d10h
tekton-pipelines-webhook 1/1 1 1 4d10h.1b — tkn CLI (Tekton Pipelines CLI)
macOS:
brew install tektoncd-cliLinux (no Homebrew) — pin the version, matching the tag you check at github.com/tektoncd/cli/releases (v0.46.0 as of this writing; swap x86_64 for aarch64 on ARM):
curl -LO https://github.com/tektoncd/cli/releases/download/v0.46.0/tkn_0.46.0_Linux_x86_64.tar.gz
sudo tar xvzf tkn_0.46.0_Linux_x86_64.tar.gz -C /usr/local/bin/ tknVerify:
$ tkn version
Client version: 0.46.0
Pipeline version: v1.6.01c — tkn-pac CLI (Pipelines-as-Code plugin)
This is a separate binary/cask from tkn above, despite the similar name. tkn-pac bootstrap (used later, in Step 3) and tkn-pac describe/resolve (used later, in Steps 5–9) all specifically need this one.
Get HyunWoo Lee, Yet Another Software Engineer’s stories in your inbox
Join Medium for free to get updates from this writer.
macOS (Homebrew cask, not a plain formula — the tap needs an explicit trust step on Homebrew 6.0+):
$ brew tap openshift-pipelines/pipelines-as-code
$ brew trust openshift-pipelines/pipelines-as-code
$ brew install --cask openshift-pipelines/pipelines-as-code/tektoncd-pac
$ xattr -d com.apple.quarantine "$(brew --prefix)/bin/tkn-pac" # Gatekeeper, first run onlyIf go-lang is already installed, installing it as a Go application builds the binary from the module directly. (requires go >= 1.26.5)
$ go install github.com/openshift-pipelines/pipelines-as-code/cmd/tkn-pac@v0.50.0% go version -m tkn-pac
tkn-pac: go1.26.7
path github.com/openshift-pipelines/pipelines-as-code/cmd/tkn-pac
mod github.com/openshift-pipelines/pipelines-as-code v0.50.0 h1:LCLfZaoGCf3RE2zHUAisCO/aAYK34z1plK4fA+9laGk=1d — Install PAC itself
https://pipelinesascode.com/docs/installation/installation/
kubectl apply -f https://raw.githubusercontent.com/tektoncd/pipelines-as-code/stable/release.k8s.yaml
kubectl -n pipelines-as-code wait --for=condition=Available --timeout=180s deployment --allVerify:
$kubectl get deployment -n pipelines-as-code
NAME READY UP-TO-DATE AVAILABLE AGE
pipelines-as-code-controller 1/1 1 1 2d12h
pipelines-as-code-watcher 1/1 1 1 2d12h
pipelines-as-code-webhook 1/1 1 1 2d12hStep 2 — Expose the Controller to GitHub
This step gets a public HTTPS endpoint in front of the PAC controller so GitHub can actually deliver webhooks to it. Nothing GitHub-App-specific happens yet — that’s Step 3, and it depends on the endpoint this step produces being reachable first.
Option A — Ingress
Example (adapt <your-ingress-class> / <your-host> to your cluster):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pipelines-as-code-controller
namespace: pipelines-as-code
spec:
ingressClassName: <your-ingress-class>
rules:
- host: <your-host>
http:
paths:
- path: /pac
pathType: Prefix
backend:
service:
name: pipelines-as-code-controller
port:
number: 8080kubectl apply -f ingress.yamlStep 3 — Create the GitHub App
Creating the App
Automated (recommended):
$ tkn-pac bootstrap github-app \
--route-url https://<your-host>/pac \
--skip-install \
--github-application-name <globally-unique-name>--skip-install since PAC's already installed (Step 1 of the full guide). This opens a local relay page; click through to GitHub's App-manifest flow, click Create GitHub App. It creates the App and the cluster secret in one shot.
Manual (if tkn-pac bootstrap isn't an option — no browser reachable from this machine, the local relay page can't reach GitHub, or you want explicit control over the App's permissions instead of trusting the manifest):
- Go to
https://github.com/settings/apps/new(personal account) orhttps://github.com/organizations/<org>/settings/apps/new(org-owned App — use this if either repo lives in an org, not a personal account). - Fill in:
* GitHub App name: any globally-unique name.
* Homepage URL: anything valid, e.g.https://github.com/<org>.
* Webhook → Active: checked. Webhook URL:https://<your-host>/pac(same endpoint verified in Step 2).
* Webhook secret: generate one yourself and save it —openssl rand -hex 20— you'll need this exact value in the Secret below.
* If using a self-signed cert (no cert-manager), also uncheck SSL verification further down the page — easy to miss since it’s below the fold. - Repository permissions (Metadata is the only read-only one; every other one PAC needs is Read & write):
* Checks: Read & write
* Contents: Read & write
* Issues: Read & write
* Metadata: Read-only
* Pull requests: Read & write
* Commit statuses: Read & write - Subscribe to events: Check the boxes for
Check run,Check suite,Commit comment,Issue comment,Pull request,Push. (If a permission from step 3 hasn't been granted yet, GitHub hides its matching event checkbox — grant the permission first, then come back to this section.) - Where can this GitHub App be installed?: “Only on this account” is fine — you’re installing it yourself below, not distributing it publicly.
- Click Create GitHub App. On the App’s page that follows:
* Note the App ID shown near the top.
* Scroll to Private keys → Generate a private key — downloads a.pemfile. Treat it like any other credential (see Security notes at the end).
Build the cluster secret from these three values:
$ kubectl create secret generic pipelines-as-code-secret \
-n pipelines-as-code \
--from-literal=github-application-id=<APP_ID> \
--from-file=github-private-key=<path-to-downloaded-pem> \
--from-literal=webhook.secret=<the-secret-you-generated-above>
$ rm -f <path-to-downloaded-pem>The key names above (github-application-id, github-private-key, webhook.secret) must match exactly — they're read by name, not positionally.
Verify the secret exists with the right keys (applies to both the automated and manual path):
$ kubectl -n pipelines-as-code describe secret pipelines-as-code-secret
Name: pipelines-as-code-secret
Namespace: pipelines-as-code
Labels: app.kubernetes.io/part-of=pipelines-as-code
Annotations: <none>
Type: Opaque
Data
====
github-application-id: 7 bytes
github-private-key: 1675 bytes
webhook.secret: 40 bytesInstalling the App (manual, via GitHub UI)
tkn-pac bootstrap (the automated path above) creates the App but does not install it anywhere — that's why it's called with --skip-install. Whichever creation path you used, the install itself happens here, once, in the GitHub UI. Read Gotcha #1 below before clicking anything — the whole point of walking through this manually is to select both repos in one visit, not two.
- Go to your App’s public page — the URL is
https://github.com/apps/<your-app-name>(the same<globally-unique-name>you set when creating it). Alternatively: Settings → Developer settings → GitHub Apps → your App and click Install App in the left sidebar. - Click the green Install button.
- If the App belongs to an org, you’ll be asked which account to install it on — pick the org (or your personal account) that owns
app-repoanddeployment-repo. - On the “Install … on account” screen, choose repository access:
* All repositories — simplest, no list to maintain, but grants access to every repo in the account/org, including ones this pipeline has nothing to do with.
* Only select repositories — pickapp-repoanddeployment-repofrom the dropdown in this same screen, before clicking Install. (Ifdeployment-repodoesn't exist yet because you haven't reached Step 4, either create it first and come back here, or install now with justapp-repoand immediately continue to Gotcha #1's instructions once Step 4 exists — the point is to select both together on one visit to this configuration, not to block on Step 4 if that's inconvenient.) - Click Install.
- You’re redirected to the App’s installation settings page (
https://github.com/organizations/<org>/settings/installations/<id>or the personal-account equivalent) — confirm the repos you selected are listed under "Repository access".
Verify:
https://github.com/settings/installations







