Pipeline as Code
Pipeline as code means keeping the definition of your build, test, and deployment process in the repository alongside the software it builds, rather than configured in a tool’s interface. The pipeline is then a file that is reviewed, versioned, and branched like anything else in the repository.
The phrase is an industry term rather than a standardized one: no specification defines it, and every product implements it with its own file name and syntax. What the practice changes is more useful than a precise definition.
Four things it changes
- The pipeline becomes reviewable. A change to how software is built and deployed arrives as a diff that someone approves, instead of as an edit someone made in a web form at four in the afternoon. This is the largest single benefit and the reason the practice spread.
- It has a history. “When did this step get added, and why” has an answer, which matters most during the incident where a pipeline change turns out to be the cause.
- It travels with the code. A branch carries the process appropriate to it, so a change that needs a new build step can bring that step with it rather than requiring a coordinated edit somewhere else.
- It can usually be tested before merging โ when the trigger you are using runs the definition from the proposed branch. That is the common case and not a universal one, which the next section unpacks.
The third item is the feature and also the trap, and the trap has two sides that pull in opposite directions. Three revisions are in play on any given run and they need not be the same one.
- The workflow revision โ which copy of the definition executes. Chosen by the trigger.
- The source revision โ which code gets checked out and built.
- The template revision โ which version of any shared or external definition the pipeline includes.
When the first two coincide, the proposed code controls the process that validates it: a pull request can modify the very checks meant to approve it. On GitHub, the ordinary pull_request trigger runs the workflow file from the merge commit of the pull request, and by default the checkout also uses that merge commit, so the code checked out and the workflow that runs are consistent. How much the platform trusts that code depends on where it comes from. For a pull request opened from a fork, the merge commit is controlled by someone without write access to the base repository, so GitHub restricts these events to a read-only GITHUB_TOKEN, withholds access to other secrets, and applies fork approval policies.
Those restrictions are the protection applied to fork-origin pull requests, and assuming them everywhere leads to the wrong conclusion in the ordinary case. A pull request opened from a branch in the same repository is not automatically demoted to a read-only token with no secrets by this mechanism. What it gets depends on the repository and workflow permission settings, the organization’s policy, and how secrets are scoped, for example whether they sit behind an environment with required reviewers. A validation job on an internal pull request therefore has to be evaluated on its own permissions, not assumed harmless. The self-modification problem above applies to both cases; only the credentials differ.
The pull_request_target trigger breaks the assumption that validating a branch validated its pipeline. With it, the workflow, and any later actions/checkout call that does not specify a ref, is taken from the base repository’s default branch, not from the pull request. That is what makes it safe to grant secrets and a writable token there. A pipeline change on a branch may therefore not have run under that trigger, and the risk inverts: the danger becomes a privileged workflow that has been pointed at the fork’s code. GitHub’s guidance is to avoid this trigger if it is not necessary; the Securely using pull_request_target page in References gives the full conditions.
Two questions follow, and they are worth asking of your own repository rather than assuming. Did the definition I changed actually execute on this branch, under this trigger? And is any privileged trigger checking out code that a contributor controls? The general shape of the risk, including what a job’s identity can reach, is worked through in Who Runs Your Build, and What Can It Reach.
The review benefit also depends on treating these diffs as real changes rather than as configuration noise. A team that reviews application code carefully and waves through pipeline edits has kept the mechanism and discarded the point โ and since the pipeline is a path to production, that is where a reviewer’s attention is worth most. The branch and merge discipline is the same; only the file is different.
What it does not decide
The phrase suggests the whole delivery process is now in the repository, and it is not. Four things sit outside the file and are usually more consequential than anything in it.
- Where it runs. The definition names a kind of machine; which machine, how isolated, and whether it is reused between jobs are properties of your build agent arrangement.
- What it is allowed to do โ mostly. The identity a job carries comes from the platform, and so do the defaults and the grants attached to it, so a pipeline that looks harmless can be running with authority nobody reviewed. But the file is not a bystander: on GitHub the recommended arrangement is to default the token “to read access only for repository contents” and then raise it where needed, since “the permissions can then be increased, as required, for individual jobs within the workflow file,” and requesting a federated identity token is likewise something the definition asks for. So effective permissions are the product of platform policy, defaults, identity grants, and the declaration in the file โ auditing either side alone misses changes made on the other.
- Which secrets it can read. The file references them; the store and its access rules live elsewhere, which is the subject of secrets management.
- The state it acts on. Deploying to an environment changes something durable, and the file describes the action rather than the thing acted upon. That is the same gap that makes infrastructure as code more than a syntax choice.
Versioned pipeline files make declared steps reviewable. To reconstruct a run, also identify the resolved templates, actions, scripts, images, and relevant inputs; a file that references external code is not a complete description of that code. Permissions, execution infrastructure, and deployment state still need their own records.
Three questions worth answering early
- Who reviews pipeline changes, and to what standard? If the answer is “whoever reviews the code,” decide whether that person knows what the delivery system grants.
- What can a branch’s definition not override? Every platform has an answer; knowing yours is how you avoid the case where a contribution edits its own checks.
- What happens when fifty repositories need the same change? If the answer is fifty pull requests, you want the shared form of this โ a pipeline template โ before the fiftieth repository exists.
A last note on the family of terms. Pipeline as code, infrastructure as code, and policy as code share one logic โ put the thing in version control so it is reviewed, diffable, and reproducible โ applied to three different objects: the delivery steps, the resources, and the rules. The benefits and the discipline transfer between them; the tooling does not, and neither does the failure mode. What the practice is in service of is continuous integration and the delivery it makes possible, rather than an end in itself.
References
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
