Modules · wrap-up
Cheat sheet & self-check
Every command from this section on one page.
Structure
.github/workflows/*.yml | Where workflows live |
on: [push, pull_request, workflow_dispatch, schedule] | Events that trigger a workflow |
jobs.<id>.runs-on: ubuntu-latest | Which runner (label) runs the job |
jobs.<id>.needs: [build] | Run after another job (otherwise jobs run in parallel) |
permissions: { contents: read } | Least-privilege GITHUB_TOKEN |
Data & speed
${{ github.sha }} ${{ secrets.X }} ${{ vars.Y }} ${{ needs.build.outputs.digest }} | Contexts in expressions |
echo "name=value" >> "$GITHUB_OUTPUT" | Set a step output |
actions/cache / setup-* with cache: | Reuse dependencies between runs |
actions/upload-artifact / download-artifact | Pass files between jobs or keep build outputs |
concurrency: { group: deploy-prod, cancel-in-progress: false } | One deployment at a time |
Reuse
on: workflow_call: { inputs: …, secrets: … } | Make a workflow callable |
jobs.x.uses: org/ci/.github/workflows/build.yml@v1 | Call it (job level) |
secrets: inherit | Pass the caller's secrets (same org/enterprise) |
action.yml → runs: { using: composite, steps: … } | A composite action (reusable steps) |
Build & deploy
strategy: { matrix: { python: ["3.11","3.12"] }, fail-fast: false } | Test several versions |
docker/build-push-action → outputs.digest | Build, push, and capture the image digest |
environment: prod | Job gated by the prod environment's protection rules |
on.push.paths: [ "services/api/**" ] | Run only when relevant files change |
Install (Helm, OCI charts)
helm install arc -n arc-systems --create-namespace oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller | The controller |
helm install arc-runner-set -n arc-runners --create-namespace -f values.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set | A runner scale set |
runs-on: arc-runner-set | Workflows target the scale set by its installation name |
Inspect
kubectl get pods -n arc-systems | Controller and listener pods |
kubectl get pods -n arc-runners -w | Runner pods appearing per job |
kubectl get autoscalingrunnersets,ephemeralrunners -A | ARC's custom resources |
OIDC in a workflow
permissions: { id-token: write, contents: read } | Allow the job to request an OIDC token |
aws-actions/configure-aws-credentials (role-to-assume) | AWS: assume an IAM role |
google-github-actions/auth (workload_identity_provider) | GCP: Workload Identity Federation |
azure/login (client-id, tenant-id, subscription-id) | Azure: federated credential |
hashicorp/vault-action (method: jwt) | Vault: JWT auth role |
Subject claims to trust
repo:acme/infra:ref:refs/heads/main | Main branch of one repo |
repo:acme/infra:environment:prod | Jobs using the prod environment |
repo:acme/infra:pull_request | Pull request runs (read-only roles only) |
In the pipeline
syft ghcr.io/acme/api@$DIGEST -o spdx-json > sbom.json | Generate an SBOM |
trivy image --exit-code 1 --severity CRITICAL ghcr.io/acme/api@$DIGEST | Fail on critical vulnerabilities |
cosign sign --yes ghcr.io/acme/api@$DIGEST | Keyless signing (OIDC identity of the workflow) |
cosign attest --yes --type spdxjson --predicate sbom.json ghcr.io/acme/api@$DIGEST | Attach the SBOM as a signed attestation |
actions/attest-build-provenance (subject-name, subject-digest, push-to-registry) | GitHub artifact attestation (SLSA provenance) |
Verifying
cosign verify --certificate-identity-regexp '^https://github.com/acme/' --certificate-oidc-issuer https://token.actions.githubusercontent.com IMAGE@DIGEST | Verify a keyless signature |
gh attestation verify oci://ghcr.io/acme/api@$DIGEST --owner acme | Verify a GitHub attestation |
Isolation
Ephemeral runners (one job per runner) | No state or malware survives between jobs |
Separate runner pools/scale sets per trust level | Untrusted PRs never share runners with deploy jobs |
Runner groups → selected repositories | Limit which repos can use which runners |
Dedicated, tainted nodes for runners | Keep CI pods away from production workloads |
Workflow hygiene
uses: owner/action@<full commit SHA> | Pin third-party actions |
permissions: {} at top, grant per job | Least-privilege GITHUB_TOKEN |
Avoid pull_request_target + checkout of PR code | Classic secret-exfiltration path |
NetworkPolicy egress allow-list for runner pods | Limit where a compromised job can send data |
Server & runner
app.ini: [actions] ENABLED = true | Enable Actions (on by default in recent Gitea versions) |
act_runner register --instance https://git.internal --token <token> | Register a Gitea runner (token from the admin/org/repo settings) |
act_runner daemon | Start the runner |
forgejo-runner register / forgejo-runner daemon | The Forgejo equivalent |
.gitea/workflows/ or .forgejo/workflows/ | Workflow folders (.github/workflows is also read by Gitea) |
Offline dependencies
Mirror actions repos into the internal forge | uses: resolves to your mirrors (DEFAULT_ACTIONS_URL setting) |
Internal registry (e.g. Harbor) + pull-through/replication | Base images and build output |
Package mirrors (apt/rpm, PyPI, npm, Go, Maven) | Builds never reach the internet |
Trivy offline DB, cosign with keys | Scanning and signing without public services |
Jenkinsfile
pipeline { agent … stages { stage('x') { steps { … } } } post { … } } | Declarative skeleton |
options { timeout(time: 30, unit: 'MINUTES'); buildDiscarder(logRotator(numToKeepStr: '30')) } | Timeouts and build retention |
withCredentials([string(credentialsId: 'api-token', variable: 'TOKEN')]) { sh 'use-token' } | Credentials binding (masked in logs) |
@Library('platform-lib@v2') _ | Load a versioned shared library |
agent { kubernetes { yaml '''…pod spec…''' } } | Ephemeral pod agent (Kubernetes plugin) |
Operating
Built-in node executors: 0 | Never build on the controller |
Configuration as Code (JCasC) YAML | Controller config in Git |
plugins.txt + jenkins-plugin-cli | Pinned plugin set, baked into the image |
Back up JENKINS_HOME (jobs, credentials, config) | Restore path for the controller |