Cheat Sheets / CI/CD & GitOps

GitHub Actions cheat sheet

76 commands from every lesson of GitHub Actions — Level by Level, on one page.

01 · Workflows, events and your first pipeline

Workflow skeleton

.github/workflows/ci.ymlWhere workflow files live
on: { push: { branches: [main] }, pull_request: {} }Run on pushes to main and on pull requests
on: workflow_dispatchAdd a manual 'Run workflow' button
on: { schedule: [ { cron: '0 3 * * 1' } ] }Run every Monday 03:00 UTC
needs: testRun this job after the test job succeeds
permissions: { contents: read }Least privilege for the built-in GITHUB_TOKEN

GitHub CLI

gh workflow listWorkflows in the repository
gh workflow run ci.yml --ref mainTrigger a workflow_dispatch run
gh run list --limit 5Recent runs
gh run watchFollow a run live
gh run view <id> --log-failedOnly the logs of failed steps
gh secret set REGISTRY_TOKEN < token.txtStore a repository secret

02 · The execution model

Structure

.github/workflows/*.ymlWhere workflows live
on: [push, pull_request, workflow_dispatch, schedule]Events that trigger a workflow
jobs.<id>.runs-on: ubuntu-latestWhich 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-artifactPass files between jobs or keep build outputs
concurrency: { group: deploy-prod, cancel-in-progress: false }One deployment at a time

03 · Real pipelines

Reuse

on: workflow_call: { inputs: …, secrets: … }Make a workflow callable
jobs.x.uses: org/ci/.github/workflows/build.yml@v1Call it (job level)
secrets: inheritPass 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.digestBuild, push, and capture the image digest
environment: prodJob gated by the prod environment's protection rules
on.push.paths: [ "services/api/**" ]Run only when relevant files change

04 · Build, test and publish container images

Key actions

docker/setup-buildx-action@v3Enable BuildKit/Buildx on the runner
docker/login-action@v3Log in to GHCR, ECR or another registry
docker/metadata-action@v5Generate tags and labels from Git refs
docker/build-push-action@v6Build (multi-platform) and push; outputs the digest
aquasecurity/trivy-actionScan the image; fail on severities you choose

Useful expressions

${{ steps.build.outputs.digest }}The pushed image digest
${{ github.sha }}The commit SHA being built
cache-from: type=gha / cache-to: type=gha,mode=maxReuse layers between runs via the Actions cache

05 · Actions Runner Controller

Install (Helm, OCI charts)

helm install arc -n arc-systems --create-namespace oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controllerThe 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-setA runner scale set
runs-on: arc-runner-setWorkflows target the scale set by its installation name

Inspect

kubectl get pods -n arc-systemsController and listener pods
kubectl get pods -n arc-runners -wRunner pods appearing per job
kubectl get autoscalingrunnersets,ephemeralrunners -AARC's custom resources

06 · OIDC federation & secret hygiene

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/mainMain branch of one repo
repo:acme/infra:environment:prodJobs using the prod environment
repo:acme/infra:pull_requestPull request runs (read-only roles only)

07 · Supply chain: sign, attest, scan, verify

In the pipeline

syft ghcr.io/acme/api@$DIGEST -o spdx-json > sbom.jsonGenerate an SBOM
trivy image --exit-code 1 --severity CRITICAL ghcr.io/acme/api@$DIGESTFail on critical vulnerabilities
cosign sign --yes ghcr.io/acme/api@$DIGESTKeyless signing (OIDC identity of the workflow)
cosign attest --yes --type spdxjson --predicate sbom.json ghcr.io/acme/api@$DIGESTAttach 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@DIGESTVerify a keyless signature
gh attestation verify oci://ghcr.io/acme/api@$DIGEST --owner acmeVerify a GitHub attestation

08 · Hardening the runner

Isolation

Ephemeral runners (one job per runner)No state or malware survives between jobs
Separate runner pools/scale sets per trust levelUntrusted PRs never share runners with deploy jobs
Runner groups → selected repositoriesLimit which repos can use which runners
Dedicated, tainted nodes for runnersKeep CI pods away from production workloads

Workflow hygiene

uses: owner/action@<full commit SHA>Pin third-party actions
permissions: {} at top, grant per jobLeast-privilege GITHUB_TOKEN
Avoid pull_request_target + checkout of PR codeClassic secret-exfiltration path
NetworkPolicy egress allow-list for runner podsLimit where a compromised job can send data

09 · The air-gapped port

Server & runner

app.ini: [actions] ENABLED = trueEnable 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 daemonStart the runner
forgejo-runner register / forgejo-runner daemonThe Forgejo equivalent
.gitea/workflows/ or .forgejo/workflows/Workflow folders (.github/workflows is also read by Gitea)

Offline dependencies

Mirror actions repos into the internal forgeuses: resolves to your mirrors (DEFAULT_ACTIONS_URL setting)
Internal registry (e.g. Harbor) + pull-through/replicationBase images and build output
Package mirrors (apt/rpm, PyPI, npm, Go, Maven)Builds never reach the internet
Trivy offline DB, cosign with keysScanning and signing without public services