Skip to content
CAMPUX Cloud Bootcamp Phase Four · Class Thirty-Nine
Phase Four — Operate, Secure & AI
4 drills · 2 situations · Part IV of IX
Class Thirty-Nine · Pipelines
Class Thirty-Nine · Part IV

The parts a pipeline keeps to itself

Thirty-one pipelines that duplicate the same steps thirty-one times are thirty-one places to forget one; this part is the four tools — variables, secrets, typed inputs, and reusable workflows or composite actions — that let one change land everywhere at once, and land safely.

Phase Four · Operate, Secure, AI · Class 39 · Part D · 13 min · Assumes Class 39 Part B and Class 24

After this you can separate GitHub variables, secrets, outputs and typed inputs, and reuse workflows without confusing reuse with enforcement.

New to this? Read Class 39 Part B and Class 24 first. Already done them, or know the material? Carry on.

§1

The same eleven lines, copied into thirty-one files

A pipeline that works is a temptation. The first one takes a week to get right; the second is a copy of the first with two words changed; by the thirty-first, an organisation has thirty-one YAML files that all build, test, and deploy in almost exactly the same way — and the day someone decides every deploy must now run a security scan, that is thirty-one files to edit, thirty-one reviews to chase, and one file quietly missed that becomes the gap an auditor finds a year later. Copy-paste is not a time-saver here; it is a debt that compounds silently, paid in the one pipeline nobody remembered to change. This part is about not incurring it — about writing the shared thing once and pointing many pipelines at it.

GitHub Actions provides repository and organization variables, secrets, workflow/job/step environment variables, reusable workflows, and composite actions. Use variables for non-secret settings, secrets for credentials, and reusable components when repeated work should have one maintained definition. Neither reusable mechanism automatically enforces organization policy; configure repository rules separately.

§2

Contexts, expressions, and shell variables

GitHub Actions expressions read context values such as github (event and repository), needs (outputs from earlier jobs), vars (configuration), and secrets (sensitive settings). Each context is permitted only in certain workflow fields. When a value is empty or rejected, check the context availability for that specific field in the workflow syntax.

Inside a shell script, Bash and PowerShell use their own variable syntax. To share a value with a later step in the same job, write it to the runner environment file; to share it with a later job, define a job output. A GitHub expression, a shell variable, and an environment variable are three different things. Keep secrets out of outputs, artifacts, and logs.

Keep three value paths separate

GitHub expression context: workflow fields can read supported values from github, needs, vars, and secrets contexts.

Shell variable: a command reads its process environment using Bash or PowerShell syntax.

Passing values: the runner environment file shares a value with later steps in the same job; job outputs pass approved values to dependent jobs.

Pick the wrong brace, ship an empty string.

Table 1 — Keep configuration values in the right place
ValueUseScope
varsNon-secret configuration, such as app name or regionOrganization, repository, or environment
secretsPassword, API token, or other credentialOrganization, repository, or environment; expose only to trusted jobs
envValue passed to a command as an environment variableWorkflow, job, or step
Reusable-workflow inputTyped option accepted by workflow_callCaller supplies it to that workflow
Job outputNon-secret result passed to a dependent jobJobs connected through needs

Ask when a value becomes available and where that context is allowed. A later step can read an earlier step’s declared output using the steps context. A dependent job reads a declared job output through needs. Do not call every GitHub expression compile-time: context availability depends on the field and execution stage. Pass untrusted values through an environment variable rather than inserting them directly into shell code.

§3

Secrets: store them at the narrowest safe scope

Never commit passwords, API keys, or private tokens to a workflow file. GitHub secrets can be scoped to a repository, organization, or environment. Use the narrowest scope needed and restrict which workflows or environments can access them. Forked pull-request workflows do not receive repository secrets by default.

For Azure, prefer OIDC federation as taught in Class Twenty-Three so the workflow does not store a reusable Azure client secret. If an application still needs a secret held in Azure Key Vault, authenticate with a narrowly scoped federated identity and retrieve only what the job needs at run time. Log masking is a backstop, not permission to print secret values.

GitHub variable and secret
A variable stores non-sensitive configuration such as an app name. A secret stores a credential or key. Both can be scoped to a repository, organization, or environment; GitHub variables and secrets do not automatically link to Azure Key Vault.

For Azure login, prefer the Class 23 OIDC pattern so the workflow does not need a reusable Azure client secret. If an application must read a secret from Azure Key Vault, use OIDC to authenticate first and grant the workflow identity only the Key Vault permissions it needs. Keep other credentials in narrowly scoped GitHub secrets.

Play

Play it through

Rotate the exposed credential, remove its literal value from YAML, and follow an explicit GitHub OIDC-authenticated Key Vault fetch. The trusted step needs a scoped data role and a network path. Never echo the value; masking is an extra guard.

§4

Reusable-workflow inputs — options a caller may set

A reusable workflow declares typed inputs under on: workflow_call, then the caller supplies values under with:. Keep this interface separate from variables and secrets. Inputs describe options the reusable workflow accepts; secrets remain sensitive values and must be scoped to trusted jobs.

Inputs to a reusable workflow can be typed as strings, numbers, or booleans and are declared under workflow_call. The calling job supplies those values under with:. Regular variables hold configuration values; they do not determine the reusable workflow interface. Keep the interface small and validate what callers are allowed to select.

First result · one ordinary input

Change one value from the caller

# .github/workflows/deploy.yml  (the reusable one)
on:
  workflow_call:
    inputs:
      environment: { type: string, required: true }
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to ${{ inputs.environment }}"

# .github/workflows/release.yml  (the caller)
jobs:
  test-env:
    uses: ./.github/workflows/deploy.yml
    with: { environment: test }

Run it and the log says Deploying to test. Change test to staging in the caller only, predict the log line, and run again. Nothing in the reusable file changed. Get this working before secrets (§3) and job outputs, where the timing of when a value exists starts to matter.

§5

Reuse work safely — reusable workflows and composite actions

GitHub Actions has two common ways to share workflow logic. A reusable workflow is called at the job level and can define one or more jobs; a composite action packages steps that run inside a job. Both reduce copy-and-paste, but neither one alone forces every repository or caller to use the shared logic. Configure repository rules and protected environments separately when a control must be mandatory.

A reusable workflow is called at the job level and can contain one or more jobs. A composite action is called inside a job and packages repeated steps. Reuse reduces drift, but neither one automatically prevents a caller from adding other workflow logic. Use repository or organization rules and protected environments when enforcement is required.

Reusable workflow
Called from a job with uses:. It can own a multi-job process and accepts declared inputs and secrets.
Composite action
Called from a step with uses:. It packages steps for reuse inside a job; it does not create a separate job.
Enforcement
Repository rulesets, required status checks, permissions, and protected environments make controls mandatory. Reuse alone does not.

This example shares the build job. A caller can still define other jobs, so the repository must enforce any security requirement separately:

# .github/workflows/shared-build.yml — reusable workflow
name: Shared build
on:
  workflow_call:
    inputs:
      app-name:
        required: true
        type: string
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: echo "Build the requested app"

# .github/workflows/warehouse.yml — caller
name: Warehouse
on:
  push:
    branches: [main]
jobs:
  build:
    uses: ./.github/workflows/shared-build.yml
    with:
      app-name: basecamp-warehouse

The shared workflow reduces duplicated logic. Callers may still add other jobs unless repository policy prevents it, so keep release enforcement in protected settings.

Case File · Campux Retail

Thirty-one pipelines become one template, and the warehouse config leaves the YAML

Basecamp's estate factored down — shared workflow logic, and a secret retrieved only when needed

Basecamp Outfitters arrived in the estate with thirty-one pipelines that build and deploy in almost exactly the same way — which meant that when Campux's standard added a mandatory security scan, it was thirty-one edits and one near-certain miss. The reader stops the bleeding the way this part teaches: the common build-and-deploy sequence is lifted into a single shared template, and the thirty-one pipelines are rewritten to reusable workflow it, each collapsing to a handful of lines that pass only its own project name. The scan now lives in one shared workflow. Reuse reduces drift, but callers can add jobs or choose another path unless repository policy and protected release settings enforce the required controls.

The warehouse deploy has a second problem: its connection string and a storage key sit in plain text in the workflow. Remove those values from the file, rotate them if they were real, and grant the deployment identity only the Key Vault access required to retrieve the application secret at run time. Editing the workflow must not expose the secret value. Keep the Azure login on the Class 23 OIDC pattern and protect production through an environment whose reviewers and allowed branches are configured in GitHub settings.

On the job

The change that landed in thirty-one pipelines at once

You · Cloud Engineer · the day the standard changes

Security asks every deploy to run a new scan. Add it to the reviewed reusable workflow and check every caller’s run. Reuse reduces duplicated edits; it does not forbid a team from bypassing the shared workflow. Enforce release controls with the repository’s supported rules and protected environments, and collect evidence that the intended check actually ran.

Class 39d

Examination

Four drills, then two situations. Write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored.

Drill 01Choose the right context
A workflow step needs the repository's non-secret app name. Where should that setting normally live?
Marked

B. Use vars or an appropriate environment variable for non-secret configuration. Reserve secrets for credentials. Job outputs pass a value produced by one job to a dependent job; artifacts carry files.

Drill 02Protect the credential
A workflow must read a password stored in Azure Key Vault. Which design best avoids a long-lived Azure credential in GitHub?
Marked

B. OIDC removes the need for a stored Azure client secret. Azure RBAC and Key Vault permissions still need to be narrow. A workflow secret can be appropriate for other credentials, but do not commit it or expose it to untrusted jobs.

Drill 03Select three
Which three statements are correct about reusable workflows and composite actions?
Marked

Correct: one, three, and four. A reusable workflow provides a shared job-level workflow interface. A composite action groups steps within a job. Both support reuse; neither automatically creates an organization-wide enforcement rule. Use repository policy and environment protection for controls.

Drill 04Spot the unsafe choice
A pull request from a fork needs to run the unit tests. Which change is safest?
Marked

B. Fork code is untrusted. Run tests without deployment credentials and keep Azure access on the protected deployment path. GitHub withholds repository secrets from fork pull requests by default; preserve that boundary.

Situation 01Write before you reveal
A teammate is under deadline and about to copy the storefront pipeline for the thirty-second time, change two lines, and ship. "We can template it later — right now I just need it working." Do you let it go, or intervene?
"Later" is where this debt goes to never be paid. What is the actual cost of copy thirty-two, and is the template really slower today?
Reasoning

Concede the deadline before you argue the design. Your teammate is not wrong that shipping matters, and treating the template as a purity crusade loses the room. Start there: the goal is a working pipeline today, and you can have one. The disagreement is only about which "today" action leaves next quarter better — and copy thirty-two does not, because it adds a thirty-second place to forget the next org-wide change, exactly the debt §1 opens on.

Price the copy honestly, both sides. The copy feels free now and bills later: the day a mandatory scan or a credential rotation arrives, it is thirty-two edits and one near-certain miss — and the miss is the pipeline an auditor finds. If a shared template already exists, pointing this pipeline at it with reusable workflow is fewer lines than the copy, not more, so "template later" is slower even today. If no template exists yet, the honest move is the small one: ship the copy now because the deadline is real, but write the ticket and the calendar time to factor it — and never let "copy N" become the argument for "copy N plus one."

Offer the ten-minute version, not the lecture. If a template is close, pair for ten minutes and convert this one; if it is not, agree the copy ships and the factoring is the next task, with a name on it. Either way you have kept the teammate on side and stopped the estate quietly gaining another file no future change can safely reach. The value in this work is bought before anyone is watching — one refused paste at a time.

Situation 02Write before you reveal
Reviewing a pull request, you find the production database password typed straight into the warehouse pipeline's variables block, unmarked. Blame says it has been there for two years, across hundreds of runs. What do you do, and in what order?
Two years of runs is two years of logs. Which of your fixes has to happen first, and why does moving the password not undo anything?
Reasoning

Treat it as already disclosed, because it is. The value sat unmarked in a pipeline definition every contributor could read, in a repository history that keeps it forever, and in the output of every run since. Nobody has to have taken it for the honest answer to be that you cannot prove nobody did. So the first action is not a refactor. It is a rotation: change the password in the database, update whatever consumes it, and accept the short window of breakage while the old value is retired. Everything else you do is worthless while the old value still opens the door.

Then move the new one somewhere the same mistake is not available. Store the application password in Azure Key Vault, use the workflow's OIDC identity to authenticate, and grant it only the secret-read permission it needs. Retrieve the value at run time and pass it only to the process that requires it; never echo it or save it in an artifact. GitHub repository and environment secrets are separate storage options, not automatic Key Vault links.

Say the uncomfortable part in the review, without making it about the author. Two years and hundreds of runs means the rotation is the news, not the bad line. Write it plainly: the credential is being rotated, here is the window, here is where the value lives now, here is who can reach it. Then ask the question that stops the next one — where else in the estate does this pattern exist? One hardcoded password is rarely alone, and the search is cheaper today than the incident report is later.

Examination record · first attempt
0/4
Class 39d · Complete
Retain this much

Five things worth carrying out of this part

  1. Contexts have defined availability by workflow field. ${{ }} resolves at compile time, before the run; needs outputs as each job is dispatched; shell variables just before a step runs. Ask of every line: does the value exist yet when this line is read?
  2. The failure is silent, which is why it bites. A expression with no value is left in the output as the literal shell variable; a template or job output with no value coalesces to an empty string. No error, a blank, a green run.
  3. GitHub variables hold non-secret configuration and GitHub secrets hold sensitive values. Neither automatically links to Azure Key Vault; authenticate with OIDC and retrieve a vault secret explicitly when the workflow needs it.
  4. Parameters are typed and resolved at compile time, so they can decide what steps exist. Variables are read while the pipeline runs, so they can only decide what those steps work with.
  5. Including a template reuses a fragment and leaves the caller in charge. Extending one hands the template the whole pipeline shape — and a required-template check on a protected resource is what turns that from a habit into a gate no one can route around.
Notes
  1. GitHub secrets support repository, organization, and environment scopes. Azure OIDC can avoid storing an Azure client secret. Confirm current context availability and permissions in GitHub's workflow syntax documentation.
  2. Calling a reusable workflow is reuse, not enforcement. Require it only through applicable repository or organization policy and protect production separately with environment rules.