Pipeline as Code
Pipeline as Code keeps your build, test, and deploy path in version-controlled files beside the application, so the delivery path is something you read, review, and change like any other code.
Open almost any modern repository and you will find a file like .github/workflows/ci.yml, .gitlab-ci.yml, a Jenkinsfile, or bitbucket-pipelines.yml. That file is the whole release process written down: what runs on a pull request, what builds on merge, what deploys to staging, what waits for a human before production. Pipeline as Code is the practice of treating that file as the source of truth for how software ships, instead of clicking through a web console where the steps live as hidden state nobody can review.
Understand This First
- Version Control — the system of record the pipeline file lives in.
- Continuous Integration — the checks the pipeline file most often runs.
Context
This is an operational pattern, and it sits underneath most of the delivery automation in this section. Continuous Integration, Continuous Delivery, and Continuous Deployment all assume there is a pipeline somewhere that runs the checks and moves the artifact along. Pipeline as Code is the assumption itself: that the pipeline is defined in a file you can read.
For years, build and release logic lived inside a CI server’s web interface. Someone configured a job by filling in form fields, and the only record of what the build did was whatever that server happened to remember. The configuration could not be diffed, could not be reviewed, and vanished if the server did. Pipeline as Code moves that logic into the repository, where it gets a commit history, a review process, and the same backup and recovery as the application.
The practice belongs to a wider family. Configuration externalizes the values that vary between environments. Infrastructure as Code externalizes the servers and networks a system runs on. Pipeline as Code externalizes the path from a commit to a running release. In each case the move is the same: take operational knowledge that used to live in someone’s head or a console, and write it down as versioned, reviewable text.
Problem
A delivery process that lives in a web console is invisible. You can’t see what changed last Tuesday, who changed it, or why. A teammate can’t review the change before it takes effect. A new engineer can’t read the build to understand it. When the CI server dies, the process dies with it, and someone reconstructs it from memory and screenshots.
Agentic coding makes the cost sharper. An agent can read, draft, lint, and explain a pipeline only when the delivery path exists as a file in a repository with a stable syntax. Point an agent at a console full of form fields and it has nothing to work with. Point it at .github/workflows/, and it can read the existing workflow, propose a change, and leave a diff. So the question is: how do you make the delivery path something a teammate or an agent can read, review, and change with confidence, instead of clicking buttons in a tool and hoping the change is right?
Forces
- The delivery path is consequential: a wrong build step ships broken code, and an over-permissive one can leak secrets or deploy to the wrong place.
- A file in the repository can be reviewed, diffed, and rolled back; a setting in a web console usually cannot.
- Pipeline files reward standard syntax, but every platform has its own: GitHub Actions YAML, GitLab CI, a
Jenkinsfile, Tekton resources, CircleCI config. Switching platforms means rewriting the file. - The pipeline must reference secrets and request permissions, yet the file itself is committed and visible to everyone with repository access.
Solution
Define the pipeline in version-controlled files that live beside the code, and change those files through the same review process the code uses.
Put the pipeline definition in the repository. Most platforms already expect this: GitHub Actions reads workflow files from .github/workflows/, GitLab runs the jobs declared in .gitlab-ci.yml, Jenkins reads a Jenkinsfile, CircleCI reads .circleci/config.yml. Adopting the pattern is often less about new tooling and more about deciding that the file in the repository, not the console, is authoritative, and that nobody edits the running pipeline by hand.
Review pipeline changes like code changes. A modification to the build, a new deploy stage, a loosened permission: each goes through a pull request, gets read by a teammate, and merges only after the checks pass. The delivery path stops being a thing that mutates silently and becomes a thing with a commit history and an author.
Keep secrets out of the file. Because the pipeline is committed, anything written into it is visible to everyone who can read the repository. Store credentials in the platform’s secrets store or an external secrets manager, and have the pipeline reference them by name. Follow the same instinct for permissions: a committed pipeline declares the access it needs, so apply least privilege and grant each stage only what it uses. Approval gates for risky stages, like production deployment, are declared in or beside the file so the gate travels with the rest of the process.
Validate the file before it merges. A pipeline definition is code, and like any code it can be syntactically valid while still wrong: a bad branch trigger, a missing check, a typo in a job name. Lint it, run the platform’s schema validation, and where possible run the same commands locally that the pipeline will run. The point is to catch the broken pipeline in review, not in production.
A committed pipeline file is readable by everyone with repository access, including agents and, for public repos, the whole internet. Never paste a token, password, or key into it. The single most common Pipeline as Code mistake is a secret hardcoded into a workflow file, scraped within hours of the first push.
How It Plays Out
A small team runs its deploys from a hosted CI server’s dashboard. One Friday the deploy starts failing and the only person who knows the job’s settings is on vacation. Nobody can see what the job does, because its steps live in form fields behind a login. After the incident, the team moves the whole process into a .gitlab-ci.yml file. Now the build is a reviewable artifact: the next time something breaks, anyone can open the file, read the stages, check the git blame, and see exactly what changed and when.
A platform team standardizes on GitHub Actions and keeps every service’s workflow in .github/workflows/. When they tighten their security policy to require image scanning before any deploy, they make the change as a pull request against the shared workflow template, review it, and roll it out across services through normal merges. The policy change has an author, a date, and a diff, the same as a code change.
A developer asks an agent to add a staging deploy to an existing pipeline. Because the workflow already lives in the repository, the agent reads .github/workflows/deploy.yml, sees the existing build and test stages, and proposes a new deploy-staging job that reuses the project’s conventions. It references the staging credentials by their secret name rather than inlining them, and leaves the change as a diff. The developer reviews it like any other pull request. None of that is possible if the pipeline is locked inside a console, because the file being code is precisely what gives the agent something to work on. This pattern is the premise; Pipeline Synthesis is the act of generating the file once that premise holds.
“Our pipeline lives in .github/workflows/ci.yml. Add a job that builds the Docker image on merge to main and pushes it to our staging registry. Reference the registry credentials by their existing secret name — do not inline any values. Set the job’s permissions to the minimum it needs. Leave the change as a diff for review.”
Consequences
Benefits. The delivery path becomes legible. It has a history, an author for every change, and a review step, so a build that used to be a black box turns into something a teammate can read and reason about. The same file is portable in the sense that it travels with the repository: clone the repo and you have the pipeline. And because the definition is text, agents can participate, reading the pipeline, drafting changes, and explaining what a stage does, in a way that a console-defined process never allows.
Liabilities. A committed pipeline file is a standing invitation to leak a secret, and the discipline to keep credentials out of it has to hold every time. The file is also tied to its platform’s syntax: a Jenkinsfile doesn’t move to GitHub Actions without a rewrite, so the portability is to the repository, not across vendors. And a pipeline written down is still a pipeline that can be wrong — valid syntax is not a correct release process, which is why the review and validation steps carry real weight rather than being a formality.
Pipeline files are production infrastructure that needs ownership and upkeep. As build tools, deployment targets, and policies change, the file drifts out of date, accumulates dead stages, and grows steps nobody recalls adding. It won’t prune itself. Treat it as code that ages, and prune it on the same schedule you would prune the rest of the codebase.
Related Articles
Sources
- Jez Humble and David Farley’s Continuous Delivery (Addison-Wesley, 2010) established the deployment pipeline as a first-class, versioned artifact, the idea that the path from commit to release should itself be defined and treated as code.
- Kief Morris’s Infrastructure as Code: Managing Servers in the Cloud (O’Reilly, 2016) developed the broader discipline of defining operational concerns as version-controlled, reviewable code, the tradition Pipeline as Code belongs to.
- The “Pipeline as Code” framing was popularized in the continuous-delivery practitioner community as CI/CD platforms moved pipeline definitions out of server dashboards and into repository files such as the
Jenkinsfile,.gitlab-ci.yml, and GitHub Actions workflow files. - Adam Wiggins’s The Twelve-Factor App (2011) argued for keeping operational concerns in versioned, environment-aware form rather than in ad hoc server state, an instinct Pipeline as Code applies to the build and release path.