CI/CD Delivery System Comparison
A useful CI/CD comparison asks where a job executes, who maintains its isolation, and which permissions it can obtain. GitHub Actions, GitLab CI/CD, Azure Pipelines, and Jenkins distribute these responsibilities differently. Compare the execution arrangement and effective access for the pipeline you intend to run.
The comparison below separates hosted services from self-managed execution and distinguishes automatic job tokens from other credentials and internal build authorization. Product defaults and limits need checking for the selected service, runner type, and deployed version.
Where the build runs, and who guarantees isolation
Every one of these systems executes your build on a build agent โ runner, agent, node, executor, depending on the vocabulary. The question that matters is whether the isolation of that agent is a property of the service or a job you have taken on.
GitHubโs secure-use guidance contrasts disposable hosted execution environments with self-hosted runners that can retain a compromise across jobs. Disposal reduces persistence inside the discarded environment. It does not restrict a job to information present at startup: code can fetch secrets or reach other services during execution. Shared caches and external effects need separate controls.
The execution boundary depends on the selected runner type. GitHubโs runner reference describes each hosted runner as a new virtual machine except for single-CPU runners; the single-CPU ubuntu-slim type runs in a container on a shared VM. Name the runner type when assessing isolation, and check the guarantees and features documented for that type. A container-based type is not thereby unsafe: the relevant question is whether its specific boundary meets the jobโs requirements.
GitHub strongly discourages self-hosted runners for public repositories because contributor-controlled code can compromise their environment. Private repositories also require care: a contributor who can cause code to execute may reach the runnerโs credentials and network. Review trigger and approval rules together with the isolation of the selected runner.
Jenkins itself is software an operator deploys, rather than a first-party hosted CI service. An organization can operate it directly or contract a third party; the operating agreement must assign isolation responsibility. Jenkinsโ controller-isolation guidance points out that job configurators, build-script authors, and test-code authors can all influence executed commands.
Jenkins initially permits builds on its built-in node. Those builds share the Jenkins processโs access to the controller filesystem. Its documentation recommends separate agents; verify filesystem separation, privilege escalation paths, and agent-to-controller protections rather than treating a different node label as proof of isolation.
GitLab.com hosted runners provision a dedicated new VM for each job. Azure DevOps Services Microsoft-hosted agents likewise provide a fresh VM per job and discard it afterwards; jobs can run directly on that VM or in a container. These hosted contracts differ from choosing your own GitLab runner or Azure agent, where you must design cleanup, sharing, and network access. Azure DevOps Server and a self-managed GitLab installation also add service-operation responsibilities. For each option, identify what contributor-controlled code can reach and who maintains that boundary.
Whose authority does a job carry
GitHub Actions, GitLab CI/CD, and Azure Pipelines generate job access tokens automatically. Jenkins needs a separate comparison: external credentials supplied to a job, the operating-system account of its agent, and authorization for actions within Jenkins are distinct sources of authority.
| System | Where the job’s authority comes from | Default reach |
|---|---|---|
| GitHub Actions | A GitHub App installation access token, generated for each job | The workflow repository, limited by effective token permissions |
| GitLab CI/CD | The triggering userโs access level, restricted to the job tokenโs supported resources | Required user privileges and target access rules apply; cross-project access normally needs allowlisting, with exceptions for some public/internal resources |
| Azure Pipelines | Job scope selects a build service identity; its grants determine resource permissions | Check scope, actual grants, and YAML repository protection/authorization. Project-scoped identities can receive cross-project grants |
| Jenkins | External credentials are configured separately from agent OS privileges and Jenkins build authorization | Builds default to internal SYSTEM authorization within Jenkins. External access depends on supplied credentials and the agent environment |
The first two rows compare automatic tokens, not the complete reach of a compromised job. GitHubโs token is repository-scoped; GitLabโs depends on the triggering user and the job-token resource restrictions. The userโs additional privileges matter only for operations and targets those restrictions admit. In Jenkins, internal SYSTEM build authorization can permit actions such as starting other builds; configure build authorization and relevant plugin controls separately from external credentials.
A third-party action can compromise credentials made available to its execution environment, including values obtained indirectly through shared files, processes, or later steps. This is different from automatically receiving every secret registered for a repository. A user able to change and run workflows may be able to arrange broader secret access; that capability should not be confused with the inputs of one fixed action invocation.
Both are short-lived, which limits exposure after the fact. GitLab’s token “is valid only while the job is running. After the job finishes, the token access is revoked and you cannot use the token anymore.” GitHub’s “expires when the job finishes or after its effective maximum lifetime” โ with the maximum tied to the runner: hosted jobs cap at six hours, while for self-hosted runners the job may run up to five days but the token “can only be refreshed for up to 24 hours.”
Apply least privilege to the Azure build identity, its resource grants, and the pipelineโs repository-access settings. Collection scope can create broad reach, but it does not override other access restrictions. An inherited installation needs an inspection of its effective configuration rather than an assumption about defaults.
A project-scoped Azure build identity can receive explicit grants in another project. Separately, Protect access to repositories in YAML pipelines restricts access to repositories referenced through checkout or uses and subject to the required authorization. Microsoft documents that protection as enabled by default for organizations and projects created after May 2020. Check both identity grants and these pipeline controls, including grants maintained outside the pipelineโs own project.
One GitHub detail deserves separate mention because it defeats a reasonable assumption: “an action can access the GITHUB_TOKEN through the github.token context even if the workflow does not explicitly pass the GITHUB_TOKEN to the action.” Not passing the token is not the same as withholding it. Restricting what the token can do is the control that works, which is why the guidance is to “make sure that actions only have the minimum access they require by limiting the permissions granted to the GITHUB_TOKEN.”
Audit everything the job can obtain: automatic tokens, supplied secrets, federated credentials, agent-resident credentials, and reachable services. A narrow token does not establish a narrow job boundary. Conversely, a secret existing in the repositoryโs settings does not by itself show that its value reached this job.
- Secrets delivered to the job, or recoverable through its execution environment. Ordinary secrets reach actions through inputs or environment variables; trigger restrictions, environment approvals, organization scope, and reusable-workflow secret passing affect availability. Review shared processes and files as well as explicit inputs.
- Cloud roles assumed through federated identity. A job that exchanges an identity token for a cloud role reaches whatever that role permits, which is frequently far more than one repository.
- Other tokens stored as secrets โ a personal access token, a registry credential, a deployment key โ each with its own, unrelated scope.
- Whatever the agent itself can reach on the network, which for a self-hosted runner inside a corporate network is a question about the network rather than about the token.
Consider a hypothetical pull-request pipeline. A test job uses a read-only automatic token and receives no deployment secret. A separate approved deployment job fetches the tested artifact and requests an OIDC identity token. The cloud trust policy must accept the intended workflow and context before issuing a role; that role then bounds cloud operations. Check runner network access and whether untrusted artifacts or shared caches can influence the privileged job. This trace connects product controls to the actual pipeline. The surrounding credential lifecycle is secrets management; product differences still matter when they determine who can obtain a cloud role and under which conditions.
Reuse is a governance mechanism
All four support pipeline as code โ definitions living in the repository, versioned with what they build. All four also have some form of pipeline template so that fifty repositories do not each maintain their own copy: GitHub has reusable workflows and composite actions, GitLab has includes and extends, Azure Pipelines has templates, Jenkins has shared libraries.
Comparing their syntax is not worth the page. What is worth deciding is what you intend the mechanism to do, because these features are how a platform team either enables or controls โ and the two need different designs.
- As a convenience, a template is a starting point teams copy and diverge from. Cheap, and it means a fix has to be applied fifty times.
- As a standard, a template is referenced and maintained as a dependency. Consumers receive a change when their selected reference resolves to it; immutable pins require an explicit update, and existing pipeline snapshots may retain the older definition.
- As a control, the template participates in an enforced route to a protected resource. For example, deleting a scan from a copied or included definition must not still allow deployment credentials to be obtained. The resource-access or release control must verify the required path and evidence; a shared file alone cannot enforce it.
A team that has not decided which of the three it is building usually gets the first while believing it has the third. And whichever it is, the template is production code with consumers: it needs a version, an owner, a test, and a deprecation path, exactly as branch and merge discipline applies to anything else many people depend on.
Artifacts, and where the boundary sits
All four can retain build outputs, directly or through integrations. Decide which outputs are temporary job artifacts and which are retained release packages; storage and retention arrangements vary. Promote a selected build artifact using a content digest or an immutable version-to-content mapping. A rebuild producing identical bytes has the same content identity; record its separate invocation, provenance, and approvals.
Two product-shaped questions are worth asking of any of them, because they are the ones that hurt later. How long are artifacts retained by default, and is that long enough to roll back to? Retention is a recovery decision, and defaults are set for storage cost. And is the thing you deploy from the same object the tests examined, or does the deployment step fetch and assemble something? The second question is really about your configuration rather than the tool, but tools differ in how easy they make the wrong answer โ the mechanics are covered under environment promotion.
Where builds must be verifiable by someone outside the team, the relevant property belongs to how you build rather than to which system runs it โ see reproducible build.
The automation itself has to be operated
The cost that gets left out of comparisons is that a delivery system is production infrastructure. When it is down, nobody ships โ including the fix for whatever is currently broken. Three burdens differ meaningfully between these systems, and none of them appears on a feature matrix.
- Agent maintenance. A hosted provider maintains the supplied execution image and its documented isolation boundary. You still own workflow dependencies, permissions, and shared resources. With self-managed agents, patching and cleanup are also your responsibility; failures can cause persistence, incorrect builds, or outages.
- The extension surface. Every one of these systems is extended by third-party code that runs inside your build. That code is a dependency with your credentials in scope, and the question to ask is not how many extensions exist but who reviews the ones you use and how you would find out one had changed.
- Upgrades and configuration decay. A hosted service upgrades under you, which removes work and occasionally changes behavior you depended on. A self-managed one upgrades when you do it. Jenkins’ documentation names the decay directly: “most Jenkins environments grow over time requiring their trust models to evolve as the environment grows. Please consider scheduling regular ‘check-ups’ to review whether any disabled security settings should be re-enabled.” That sentence generalizes to all four โ every temporary exception becomes permanent unless someone has a standing reason to look.
Two things worth having regardless of product. An audit log that answers who changed a pipeline definition, who approved a deployment, and which identity performed it โ because the delivery system is a path to production and will be asked about in any security review. And an honest accounting of operating cost, where the line items people forget are agent capacity, the people who maintain templates and plugins, and the queue time engineers spend waiting; the framing is total cost of ownership rather than license price.
Choosing, and what not to choose on
Assess the choice against source integration, effective permissions, execution placement, and operating responsibility. Their relative importance depends on the pipeline and its constraints.
- Where your source already lives. The integration you get for free by using the delivery system attached to your forge is substantial, and the coordination cost of splitting them is paid on every change.
- Whether the permission model matches your organization. An identity derived from the triggering user and an identity scoped to a repository imply different review processes and different answers to “what can this pipeline reach.” Pick the one you can reason about, then configure it.
- Whether you can accept hosted execution. If regulation or network placement requires your own agents, you have taken on the isolation obligation and should plan it rather than discover it.
- Who will operate it. Assign responsibility for upgrades, capacity, permissions, and recovery. Neglect can affect both security and availability.
Feature counts alone do not establish fit. These products can support building, verifying, and promoting a candidate, while continuous integration additionally depends on frequent integration into a shared codebase, rapid reliable checks, and repair of a broken shared build. The practice of continuous delivery keeps a verified candidate deployable under the release rules. Compare performance using your workload: separate build execution from queueing, provisioning, cache access, and artifact transfer. Syntax and migration effort also matter, but should be weighed against access control and operating requirements rather than assigned a universal learning time.
If a migration is on the table, the honest framing is that you are trading one set of defaults for another, and the work is mostly in the two structural areas above rather than in translating pipeline files. Record why you chose what you chose โ an architecture decision record is the cheapest way to save the next team from relitigating it โ and include what you decided not to optimize for.
For a proposed choice or migration, trace one representative pipeline from contributor-controlled input to production access. Record the execution boundary, resolved dependencies, effective identities, approval conditions, and recovery responsibilities. That evidence makes the comparison specific enough to review later.
References
- GitHub Docs, Secure use reference
- GitHub Docs, GITHUB_TOKEN
- GitHub Docs, Use GITHUB_TOKEN for authentication in workflows
- GitLab Docs, CI/CD job token
- Microsoft Learn, Access repositories, artifacts, and other resources
- GitHub Docs, GitHub-hosted runners reference
- Jenkins Handbook, Controller Isolation
- Jenkins โ Access Control for Builds
- GitHub Docs โ Using secrets in GitHub Actions
- GitLab Docs โ CI/CD YAML syntax reference
- Microsoft Learn โ Microsoft-hosted agents
- GitLab Docs โ GitLab-hosted runners
- Reproducible Builds โ Definitions
- DORA โ Continuous integration
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
