Six lessons, six tools and the same Reservalia pipeline translated six times. If you have got this far hoping this lesson will say which one is best, the answer is going to disappoint you and at the same time it is the most useful thing the module can give you: the question is badly framed. Not because comparing is in poor taste — we are going to compare in detail — but because "the best tool" is not a property of the tool: it is a property of the relationship between the tool and a specific context, with its code, its team, its legal constraints, its budget and what it already knows how to operate. This lesson does four things: it recaps the Reservalia pipeline across the tools we have seen and sets out a vocabulary equivalence table that will serve you for the rest of your career; it compares by dimensions, with no global winners; it ranks the decision criteria by their real weight, which is very different from the weight they get in team discussions; and it deals with what almost nobody deals with: how much it costs to change tool, how to reduce that cost before you need to, and how to migrate without bringing the team to a halt.

Contents

  1. Why "which is the best?" is badly framed
  2. The same pipeline, six times: a recap
  3. Vocabulary equivalence table
  4. Comparison by dimensions
  5. The decision criteria, ranked by real weight
  6. A decision tree
  7. Total cost of ownership, with numbers
  8. The cost of changing tool and how to reduce it
  9. Migration between tools, in phases
  10. Choosing by fashion, or by what your old company used
  11. Common Mistakes and Tips
  12. Exercises
  13. Conclusion and close of the module

  1. Why "which is the best?" is badly framed

The question assumes there is a total ordering between tools that solve the same problem. There is not, for three specific reasons.

First: the tools converge. Compare what all six do today and you will find 80 % of it overlaps: a pipeline defined in a file in the repository, jobs with dependencies, execution in containers, cache, artifacts, matrices, secrets, approvals, federated identity. The real differences are in the remaining 20 % and in how they feel to use. Choosing by feature list produces constant ties.

Second: the dominant cost is not the tool, it is the context. Migrating from one to another costs weeks; operating a self-hosted installation costs half a person; getting the team fluent costs months. Compared with that, one having a nicer matrix syntax is noise.

Third: most of the decision is already made by the time the question arrives. If the code is on GitHub, the team's identity is on GitHub and branch protection is on GitHub, choosing another CI means maintaining an additional integration forever. Almost no technical advantage makes up for that.

The well-framed questions are these, and the rest of the lesson answers them:

Useful question Where it is answered
What non-negotiable constraints do I have? (data, network, hardware, compliance) Section 5, criterion 2
Where do my code and my identity live? Section 5, criterion 1
What does my team know how to operate today, and what is it willing to operate? Section 5, criterion 3
What is the total cost, including people? Section 7
What would it cost me to change in three years' time, and how do I reduce that now? Section 8

And one question that is not useful: "which is the most powerful?". They are all powerful enough for 95 % of pipelines. Power stops being the limiting factor very early on.

  1. The same pipeline, six times: a recap

The Reservalia pipeline — install with cache → lint and test in parallel with sharding → build the image → scan → publish by digest → deploy with approval — expressed in each tool, and what each translation revealed:

Tool How it turned out What the translation revealed
GitHub Actions (baseline) needs, matrix, composite action, reusable workflow, OIDC, Environments The course's reference point
Jenkins (06-01) Declarative Jenkinsfile: stages, parallel, matrix, stash, input, post The graph is implicit (a tree, not a DAG); the input holds an executor if you are not careful; the stash goes through the controller
GitLab CI (06-02) stages + needs, parallel, services, environment, integrated registry The cache/artifacts distinction is explicit; the registry and credentials come free; rules governs everything
CircleCI (06-03) workflows with requires, parallelism, commands, orbs, contexts Completely manual cache; workspace ≠ cache ≠ artifacts; splitting by historical timings that the others do not provide
Travis CI (06-04) stages + jobs.include, fixed phases It does not quite fit: no graph, no passing artifacts between jobs, sharding copied four times, promotion by digest compromised
Kubernetes (06-05) It is not CI: it is the destination. Deployment by digest, probes, rollout undo, GitOps It changes who starts the deployment and takes the credentials out of the CI

The conclusion of the exercise is what justifies having done it six times: the pipeline is the same problem everywhere. Install reproducibly, verify in parallel, build an immutable artifact, scan it, publish it identified by its content and promote it through a gate. What changes is the syntax, the names of the pieces and what the tool gives you for free. Somebody who understands the problem translates to any syntax in days; somebody who only knows one syntax starts from scratch every time.

  1. Vocabulary equivalence table

This table is probably the most reusable thing in the lesson. Keep it.

Concept GitHub Actions GitLab CI Jenkins CircleCI Travis CI
Definition file .github/workflows/*.yml .gitlab-ci.yml Jenkinsfile .circleci/config.yml .travis.yml
A complete run Workflow run Pipeline Build / Run Pipeline Build
Unit with its own machine Job Job Stage with an agent Job Matrix job
Logical grouping (does not exist) Stage Stage (does not exist) Stage
Individual command Step script line Step Step Phase (script, install…)
Executing machine Runner Runner (+ executor) Agent / node Executor Worker
Concurrency slot (implicit) concurrent Executor (implicit) (implicit)
Dependency between units needs needs / stages Order of stages requires Order of stages
Parallel execution matrix, jobs parallel, jobs parallel parallelism, jobs Matrix entries
Dependency cache actions/cache cache: Plugin / volume save_cache/restore_cache cache:
Passing files between units Artifacts artifacts stash/unstash Workspace (does not exist)
Downloadable output Artifacts artifacts archiveArtifacts store_artifacts (external)
Value output outputs reports:dotenv env / script (file + workspace) (does not exist)
Auxiliary service services: services: Pod container Secondary container services:
Secrets secrets Protected variables Credentials + withCredentials Contexts / variables secure:
Federated identity OIDC (id-token) id_tokens Plugin / instance role OIDC (no)
Step reuse Composite action extends / include Shared Library (vars/) Command (YAML anchors)
Job reuse Reusable workflow Component / include Shared Library Orb (no)
Template registry Marketplace Component catalogue Library repository Orb registry (no)
Deployment environment environment environment (plugin) (not modelled) (no)
Manual approval Environment reviewers when: manual input type: approval (no)
Cancel stale runs concurrency interruptible disableConcurrentBuilds Auto-cancel (no)
Path filter paths rules: changes when { changeset } (your own logic) (no)

Two observations that can be read between the lines. First, the Travis column has a lot of gaps, and that is not an oversight: it is the difference between a CI tool and a CI/CD tool, and it explains its decline better than any market analysis. Second, "job" means different things: in Jenkins a job is what elsewhere is a workflow, while the unit with its own machine is the stage with an agent. It is the vocabulary confusion that causes the most misunderstandings in mixed teams.

  1. Comparison by dimensions

No global winners: each row is a dimension and what matters is which of them matter to you.

Dimension GitHub Actions GitLab CI Jenkins CircleCI Travis CI
Execution model Graph (needs) Stages → graph Tree with nested parallelism Graph from the outset Fixed phases
Hosting SaaS + your own runners SaaS or fully self-hosted Self-hosted only SaaS + your own runners SaaS
Configuration Code Code Code (with UI inheritance) Code Code
Reuse Composite + reusable + marketplace extends/include/components Shared Libraries Orbs with immutable versioning Very limited
Secrets and federated identity Native OIDC, environments Native OIDC, protected variables Plugins; not native OIDC, contexts Encrypted in the repo
Ecosystem The largest Medium, integrated The broadest for unusual integrations Medium Residual
Integrated platform High (repo, registry, issues) The most complete None: a standalone piece None: a standalone piece None
macOS / ARM / GPU Yes (macOS expensive) With your own runners Anything you own Wide offering ARM and unusual architectures
Deployment and approval maturity Environments with rules Environments with history and on_stop input, no environment model type: approval, no environment deploy providers
Pipeline observability Medium (summaries, timings) Good Depends on plugins The best Sparse
Cost model Minutes + storage Per user + minutes Infrastructure + people Credits + users Credits
Exit cost Medium High (repo + CI + registry) Medium Low Low

How to read this table without going wrong: a row only matters if it is a requirement of yours. "Integrated platform: the most complete" is an enormous advantage if you are looking for a platform and a disadvantage if you already have an ecosystem in place and only want CI. "Self-hosted only" is disqualifying for a team of three and is exactly what a company with data that cannot leave the building needs. There is no column that wins more rows and therefore wins.

  1. The decision criteria, ranked by real weight

Here is the most valuable content in the lesson, and the order matters more than the list.

Criterion 1 — Where the code and the team's identity live (decides ~80 % of cases)

If your code is on GitHub and your team authenticates with GitHub, GitHub Actions starts with an advantage that almost nothing offsets. If it is on GitLab, the same goes for GitLab CI. It is not laziness: it is that using an external CI implies, forever:

  • A permissions integration to maintain, which breaks when somebody rotates a token.
  • Identity synchronisation: who can deploy has to be defined twice, and the two definitions diverge.
  • Two places to look when something fails, and a context switch in every incident.
  • Checks arriving from outside and branch protection that depends on an integration.

That cost is permanent and silent. The technical advantage that justifies it has to be large and specific. This criterion is not a tie-breaker: it is the starting point, and the other tools have to earn their way past it.

An uncomfortable corollary: if your code is on GitHub and you are evaluating five CI tools, you are probably solving the wrong problem. Start with the integrated one, identify what you are missing in specific terms and only then look elsewhere.

Criterion 2 — Compliance and data constraints (decides, when it applies, without argument)

If there is a real legal or contractual constraint, it overrides everything else. The questions to ask before anything else:

  • Can the source code leave our network? And the logs, which often contain more than they appear to?
  • Does the test data contain personal or regulated information?
  • Is there an audit retention obligation, covering who approved what and when?
  • Is separation of duties required — that whoever writes the change is not whoever approves it?
  • Is there a data residency requirement in a specific jurisdiction?

An important and frequent warning: a self-hosted runner of a SaaS does not satisfy a strict constraint. The work runs on your network, yes, but the control plane, the orchestration, the logs and often the secrets remain with a third party. If the requirement is literal, the answer is to self-host the whole platform: Jenkins or self-hosted GitLab. And the first action is not technical: it is to read the contract or the regulation, because half the time the constraint is less strict than the team assumes, and the other half it is stricter.

Criterion 3 — What the team knows how to operate today

A tool nobody knows how to operate is worse than a less capable one the team has mastered. A Jenkins with no owner degrades on its own (06-01); a badly operated Kubernetes cluster is worse than ECS (06-05).

Specific questions, with names attached:

  • Who updates this? What do they stop doing while they do?
  • Who answers at three in the morning if the CI goes down during an incident?
  • Who restores the backup, and when was it last rehearsed?
  • What happens when that person goes on holiday or leaves the company?

If any of those has no answer, the self-hosted option is not on the table however technically convincing it is. For Reservalia — Marta, Diego and Nuria — the answer is obvious and requires no analysis: SaaS.

Criterion 4 — Hardware and platform needs

macOS to build iOS (05-02), GPUs for models, native ARM, physical devices, licences tied to a machine, lots of memory. Here the order inverts: if there is a hardware need that only one option covers, that option is in, no question, even if only for that part of the pipeline. The usual and sensible pattern is hybrid: the main tool for everything, and a specific piece where the hardware forces it.

Criterion 5 — Budget and its shape

Not just how much, but also what kind:

Shape of the cost Tools Implications
Variable opex (per minute/credit) Actions, CircleCI, GitLab SaaS Grows with activity; no up-front investment; surprises if nobody watches
Opex per user GitLab, partly CircleCI Predictable; penalises occasional contributors
Capex + people opex Jenkins, self-hosted GitLab Up-front cost and fixed headcount; predictable; independent of activity

This criterion intersects with criterion 3: the cost of people is real even though it never appears on an invoice, and it is precisely the one most often forgotten in the spreadsheets that compare "X euros a month of SaaS" with "a machine we already have".

Criterion 6 — The team's CI/CD maturity

A team starting out benefits from an opinionated tool, with sensible defaults and plenty of documentation. A mature team with particular needs values control. Adopting Jenkins with Shared Libraries and agents on Kubernetes when you are three people who have never done CI guarantees abandonment; adopting a highly opinionated tool when you have unusual requirements guarantees fighting it every week.

The summary of the order

1. Non-negotiable constraints  → if there are any, they filter first
2. Where the code lives        → decides 80 % of what is left
3. Who operates it             → rules options out, it does not pick them
4. Hardware                    → adds hybrid pieces
5. Budget and its shape        → fine-tunes among the survivors
6. Maturity                    → decides how much rope to give yourself

Note that the technical quality of the tool does not appear in the list. Not because it does not matter, but because every survivor of the six filters is good enough, and by that point the decision is already made.

  1. A decision tree

flowchart TD
    A["Choose a CI/CD tool"] --> B{"Is there a legal or contractual constraint<br/>preventing the use of a SaaS?"}
    B -->|"Yes"| C{"Who operates the platform?"}
    C -->|"Nobody with time"| D["Solve that first:<br/>there is no viable option without an operator"]
    C -->|"There is a team"| E{"Do you want a full platform<br/>or just an automation engine?"}
    E -->|"Platform"| F["Self-hosted GitLab"]
    E -->|"Engine"| G["Jenkins"]

    B -->|"No"| H{"Where does the code live?"}
    H -->|"GitHub"| I["GitHub Actions<br/>starting point"]
    H -->|"GitLab"| J["GitLab CI/CD<br/>starting point"]
    H -->|"Several or Bitbucket"| K["CircleCI or another independent<br/>SaaS"]

    I --> L{"Is something specific missing<br/>that you can name?"}
    J --> L
    L -->|"No"| M["Stay put. Invest in the pipeline,<br/>not in choosing a tool"]
    L -->|"Speed of a large suite"| N["Evaluate CircleCI<br/>or improve what you have first"]
    L -->|"Special hardware"| O["Hybrid: your own runners<br/>or Jenkins just for that piece"]
    L -->|"Many services on Kubernetes"| P["Add GitOps: Argo CD or Flux<br/>do not change CI"]

Two branches deserve comment because they are the ones most often got wrong in practice.

"Something specific missing that you can name": the requirement to name it is deliberate. "We would like something better" is not a requirement. "The e2e job takes 14 minutes because of uneven splitting and we have no timing history" is one, and — notice — it can probably be fixed without changing tool.

"Many services on Kubernetes" leads to adding GitOps, not to changing CI. It is a very common mistake: the problem is the deployment model (06-05), not the CI engine, and changing the latter does not fix the former.

  1. Total cost of ownership, with numbers

Comparing "20 euros a month" with "a machine we already have" is the most common way of getting this wrong. The correct exercise includes people.

Scenario: a team of 12 developers, 40 pipeline runs a day, 12 minutes of compute per run across 4 parallel jobs ≈ 35 machine-minutes per run.

Monthly consumption = 40 runs/day × 21 days × 35 min ≈ 29,400 machine-minutes
Item SaaS per minute Self-hosted
Compute 29,400 min × the per-minute rate 3 machines always switched on
Storage (artifacts, cache) Billed per GB Disks and backups
Licences Included or per user × 12 Depending on the tool and tier
Operation ~0 h/month 8-16 h/month: updates, plugins, agents, incidents
On-call 0 Part of a rota, or somebody de facto
Setting up Hours 2-6 weeks of one person
Cost of unavailability The provider's Yours, with the team idle for the duration

The formula to use, and the number that nearly always decides it:

TCO_self_hosted = infrastructure + licences
                + (operation_hours/month × loaded_hourly_cost)
                + amortisation(setup)
                + risk(unavailability × frequency)

TCO_saas        = compute + storage + per_user_licences
                + light_management_hours

With a loaded hourly cost for a platform engineer — salary plus taxes, tooling and space — 10 hours a month of operation typically exceed the entire SaaS bill on their own for a team of this size. That is the result that surprises people and the one to take into the conversation with whoever decides. And the break-even point shifts with size: with 200 developers and very heavy usage, the same 10-20 monthly hours are spread across far more consumption and self-hosting can win clearly.

Three warnings so the calculation is honest:

  • Do not invent rates. Each provider's rates change by plan, region and machine type; look them up the day you do the calculation and note the date.
  • Count the multipliers. macOS and large machines do not cost the same per minute; a pipeline with a lot of macOS (05-02) changes the result completely.
  • Include what the SaaS saves you in integrations. If the alternative requires maintaining identity synchronisation and webhooks, those are hours that go in the self-hosted column, not in nobody's.

  1. The cost of changing tool and how to reduce it

A CI/CD migration in a real repository takes weeks, not days, and the effort splits up in systematically the same way:

Part of the pipeline Cost of migrating Typical proportion
Build, test and deployment logic Low if it is in scripts; high if it is in the YAML 40-60 % of the content
Orchestration (jobs, dependencies, matrix, triggers) Mechanical rewriting 20-30 %
Integrations (third-party actions/orbs/plugins) Rebuilding 10-20 %
Secrets and federated identity Reconfigure and rotate 5-10 %
Team learning Weeks of reduced fluency Diffuse and real

The lever that really moves that number was flagged in 06-04 and deserves repeating: take the logic out of the YAML and leave it as a thin orchestrator.

# Fragile: 60 lines of logic inside the YAML of one specific tool
- run: |
    npm ci --prefer-offline
    npx prettier --check .
    npm run lint -- --max-warnings 0
    npx tsc --noEmit
    npm test -- --coverage --shard=${{ matrix.shard }}/4
    node scripts/check-coverage.js --minimum 80
# Makefile — a stable interface, independent of the tool
.PHONY: install quality test build image

install:
	npm ci --prefer-offline

quality: install
	npx prettier --check .
	npm run lint -- --max-warnings 0
	npx tsc --noEmit

test: install
	npm test -- --coverage --shard=$(SHARD)/$(TOTAL)
	node scripts/check-coverage.js --minimum 80

image:
	docker buildx build --file apps/api/Dockerfile \
	  --cache-from type=registry,ref=$(ECR)/$(IMAGE):cache \
	  --tag $(ECR)/$(IMAGE):$(SHA) --push .
# The YAML, in any tool, becomes trivial
- run: make quality
- run: make test SHARD=${{ matrix.shard }} TOTAL=4

The benefits, and this is the important argument: they do not depend on you ever migrating.

  • The developer runs exactly the same thing locally. The whole class of "it only happens in CI" failures disappears, and those are among the biggest time sinks.
  • The logic is reviewable, testable code, not a string inside a YAML file nobody reads in the PR.
  • The pipeline becomes readable: a ci.yml with make quality and make test is understood at a glance.
  • And yes, migrating goes from weeks to days.

The honest limit: not everything comes out of the YAML. Triggers, matrices, permissions, environments, approvals, concurrency and cache belong to each tool, and abstracting them requires a layer of indirection that costs more than it saves. The reasonable goal is not total portability, but that the bulk of the migration effort is rewriting orchestration, not rebuilding logic.

  1. Migration between tools, in phases

Once the migration is decided, the pattern that works is the same as in 05-04: increments with value of their own and without freezing the team.

Phase What you do When to move to the next Risk
1. Inventory Every workflow, what each does, who depends on it, what secrets it uses, what integrations it has. And what is dead When it is written and reviewed None; skipping it is the classic mistake
2. Parallel pipeline The new one runs alongside the old one on every PR, without being required. Results are compared 2-3 weeks with matching results Temporary double compute cost
3. Switching the required signal The required check becomes the new one; the old one becomes informational When nobody looks at the old one any more The highest: this is where you discover what was missing
4. Migrating the CD Deployment last, and environment by environment: staging first, then production When staging has been stable for weeks High: it affects production
5. Decommissioning Delete the old pipeline, switch off the service, revoke every secret, update documentation and badges Low, if the earlier phases were done

Four rules that avoid the usual disasters:

The CD is migrated last, always. CI can be duplicated with no consequences; a duplicated or half-finished deployment definitely has them. And within the CD, staging first.

Never migrate everything at once in organisations with many repositories. You pick a pilot repository — representative but not critical — learn with it, write a template and propagate it.

Secrets are rotated, not copied. It is the natural moment to do it and to discover which ones did not need to exist any more. And in phase 5, revoking the old system's secrets is part of the migration, not an extra.

What not to migrate, which is as important as what to migrate: workflows nobody looks at, jobs disabled for months, integrations with systems that no longer exist, matrices of unsupported versions, and the logic that was there "just in case". A migration is the best opportunity you will ever get to delete things; wasting it means moving the debt intact and adding the cost of the move.

  1. Choosing by fashion, or by what your old company used

Two biases that produce expensive decisions and are worth naming out loud, because they almost never are.

The conference bias. You see an excellent talk about a tool at a company with 400 engineers, five platform people and a scaling problem you do not have, and you conclude that is what you should use. The question that defuses it: what problem of mine, by name, does this solve that is not solved today? If there is no specific answer, it is admiration, not a requirement. It is the same trap 06-05 pointed out with Kubernetes, and the answer is identical: complexity is justified by a problem, not by who else uses it.

The newcomer bias. Somebody joins the team from a company where they used X and proposes migrating to X. Sometimes they are right — they bring real experience the team does not have — but the bias is predictable: they know X in depth and the current tool barely at all, so they compare the best of one with the worst of the other. The test that defuses it: ask them to master what is there first and then list, by name, what is missing. If after two months the list still stands and is specific, the proposal deserves serious evaluation. Most of the time the list shrinks to two items, and both are solved by configuring what is already there better.

And a third, subtler trap: changing tool in order to avoid fixing the pipeline. A pipeline that is slow because of an unquarantined flaky test, a badly configured cache and jobs without timeouts will still be slow in the new tool, with the difference that now nobody knows where to look. Before evaluating alternatives, apply 04-04: measure, separate queueing from execution, and fix your own side. If after that the problem persists and it is the tool's, you now have data for the conversation.

Common Mistakes and Tips

Comparing by feature list. They all have nearly everything. The decision is in the context, not in the table.

Ignoring where the code lives. It is the heaviest criterion and the one most underestimated in team discussions.

Counting only the invoice. Operation hours are a real cost; in mid-sized teams they usually exceed the SaaS that was being avoided.

Believing a self-hosted runner satisfies a compliance constraint. The control plane is still outside. Read the regulation before designing.

Migrating the CD before the CI. CI can be duplicated harmlessly; a deployment cannot.

Migrating one-to-one without taking advantage. No needs, no cache fix, nothing dead deleted: you move the debt and pay for the move.

Copying the secrets instead of rotating them. And not revoking the old system's when you are done.

Choosing a tool nobody is going to operate. A Jenkins with no owner is worse than having no CI, because it gives a signal people trust.

Confusing "more powerful" with "better for us". Power stops being the limiting factor very early on.

A cross-cutting piece of advice that sums up the module: invest in understanding the problem — immutable artifact, promotion by digest, quality gates, fast feedback, least privilege — and treat the tool as interchangeable. Somebody who has mastered the problem learns a new tool in two weeks; somebody who has only mastered one syntax starts from scratch every time they change job.

Exercises

Exercise 1. Three scenarios. For each one, apply the six criteria in order, recommend a tool (or combination) and justify which criterion was decisive and which turned out to be irrelevant:

  • (a) A 6-person startup, code on GitHub, a Node web application deployed on Vercel and an API on ECS. No legal constraints. Nobody wants to operate infrastructure.
  • (b) A 300-person company in the healthcare sector. The code cannot leave their network under contract with their clients. They already have Jenkins with 180 freestyle jobs. A 4-person platform team. They want to "modernise".
  • (c) A 40-person product company, code on self-hosted GitLab, 12 microservices on Kubernetes, an iOS and Android mobile application, and a 2-person platform team that is overwhelmed.

Exercise 2. Calculate the comparative TCO for a team of 25 developers with 80 daily runs of 15 minutes across 5 parallel jobs. Compare SaaS per minute against self-hosted Jenkins on your own machines. List every item — including the ones that appear on no invoice — and explain what assumption would have to change to reverse the result.

Exercise 3. Marta asks you for a plan: reduce Reservalia's dependency on GitHub Actions without migrating and without stopping development. Propose the specific actions, their cost, their benefit regardless of whether the migration ever happens, and where the point is at which you stop investing. Estimate the before and after of the cost of a hypothetical migration.

Solutions

Solution 1.

(a) A 6-person startup.

Criterion Result
1. Constraints None → does not filter
2. Code and identity GitHub → decisive
3. Who operates it Nobody → rules out everything self-hosted
4. Hardware Nothing special → irrelevant
5. Budget Private repository, low volume: the included minutes probably cover it
6. Maturity Low: opinionated and well documented is what you want

Recommendation: GitHub Actions, with no further analysis. Decisive: criteria 2 and 3, which point the same way. Irrelevant: 4 and, almost, 5. The advice that goes with the recommendation is worth more than the recommendation itself: with 6 people, time spent evaluating tools is time stolen from the pipeline; use the integrated one and spend that effort on putting quality gates, automated deployment and DORA metrics (01-05) in place.

(b) A 300-person healthcare company.

Criterion Result
1. Constraints The code does not leave the network → decisive, filters first: self-hosted only
2. Code and identity Depends where it is; if it is GitHub Enterprise Server or self-hosted GitLab, that points to their integrated CI
3. Who operates it 4 people: there is a team. Viable
4. Hardware To be verified; in healthcare there are usually integrations with legacy systems
5. Budget Capex + people, already accepted
6. Maturity Medium-low: 180 freestyle jobs indicate decade-old practices

Recommendation: do not choose a tool yet. The diagnosis is that "modernising" does not mean changing tool, it means moving from freestyle to pipeline as code (04-05, 06-01), and that is done inside Jenkins, without migrating anything. A phased plan: (1) an inventory of the 180 jobs, with the well-founded expectation that a third of them are dead; (2) a Jenkinsfile for the 20 most used, running in parallel with the freestyle ones; (3) JCasC and ephemeral agents to end configuration drift; (4) a Shared Library for what is repeated. When that is finished, if there are still reasons, self-hosted GitLab is re-evaluated with real data about what is missing.

Decisive: criterion 1, which rules out all SaaS. Irrelevant: any comparison with GitHub Actions or CircleCI, which fell out at the first filter. And the methodological lesson: the biggest improvement available here does not require changing tool, and proposing a migration would have burned months without solving the real problem, which is that the configuration lives in forms on a server.

(c) A 40-person product company.

Criterion Result
1. Constraints None declared
2. Code and identity Self-hosted GitLab → GitLab CI is the starting point
3. Who operates it 2 overwhelmed people → the most important data point
4. Hardware macOS for iOS → requires a specific solution
5. Budget They already pay for the instance; the margin lies in not adding operation
6. Maturity High: 12 microservices on Kubernetes

Recommendation: stay on GitLab CI and add no tool at all, with three specific actions. For macOS, macOS runners managed by a third party or GitLab's SaaS service for those particular jobs: buying Apple machines and operating them with an overwhelmed team is exactly what you must not do (05-02). For the 12 microservices, include of a shared component pinned to a version, instead of 12 divergent files (05-03, 04-05). For Kubernetes, GitOps with Argo CD or Flux, which additionally takes the cluster credentials out of the CI (06-05).

Decisive: criterion 3. The overwhelmed platform team is the constraint that governs every decision, and any recommendation that adds operational load is wrong by definition, however technically good it is. Irrelevant: comparing GitLab CI with CircleCI or Actions; no advantage would compensate for adding an external integration to a team that cannot cope.

Solution 2.

Consumption: 80 × 21 × 15 × 5 = 126,000 machine-minutes/month ≈ 2,100 machine-hours, which is equivalent to about 3 machines at 100 % occupancy, and since the load falls in working hours, in practice you need 5-6 machines to avoid queueing at peak times.

Item SaaS per minute Self-hosted Jenkins
Compute 126,000 min × the current rate 6 machines (idle overnight and at weekends)
Storage Artifacts and cache per GB Disks, backups, their storage
Network Included Egress traffic when pulling images and dependencies
Licences Per user according to plan Jenkins is free; the operating system and monitoring are not
Setting up 1-2 days 3-6 weeks of one person
Monthly operation 1-2 h (light management) 12-20 h: updates, plugins, CVEs, agents, disk
On-call 0 Part of a rota
Unavailability The provider's; no control Yours; with 25 people idle for the duration
Training Low Medium: Groovy, plugins, JCasC
Overnight saving Automatic (you do not pay for what you do not use) None: the machines are switched on regardless

Calculation: with a loaded hourly cost for a platform engineer, 16 monthly hours of operation typically come to a figure of the same order as the entire SaaS bill at this volume, before counting machines, storage and setting up. Adding the amortisation of the initial 4-6 weeks over the first year, self-hosted comes out clearly on top.

Which assumptions would reverse the result:

  1. A much larger volume. At 500,000 machine-minutes a month, operation hours do not grow proportionally and the cost per own minute drops a lot. It is the economy of scale that makes very large companies self-host.
  2. Hardware already available and amortised, with real idle capacity and staff who already operate it for other things.
  3. A compliance constraint that makes the comparison irrelevant: then it is not an economic decision.
  4. Much more powerful or specialised machines — GPU, lots of memory — where the SaaS multiplier is high and your own machine pays for itself quickly.
  5. Very long, continuous jobs that keep the machines busy outside working hours too, removing the advantage of paying only for what you use.

A defensible conclusion: for 25 developers and this volume, SaaS, and the decisive argument is not the compute bill — which may even be higher — but the 12-20 monthly hours of a person you do not have to dedicate to operating CI. That is the number to take to the meeting.

Solution 3.

A well-framed goal: it is not "being able to migrate", it is reducing coupling in exchange for benefits that pay off from day one. If an action only delivers value in the event of a migration, it does not go in the plan.

# Action Cost Immediate benefit (even if we never migrate) Reduction in migration cost
1 Move the logic of the long run blocks into scripts/*.sh and npm scripts, with a Makefile as the interface (make quality, make test, make image) 3 days Runnable locally: the "only in CI" failures disappear; logic that is reviewable and testable; a readable ci.yml High
2 Replace third-party actions that only wrap a CLI with the CLI itself; pin the remaining ones by SHA and document why each is there 2 days Less supply surface (04-03); fewer breakages from other people's changes Medium-high
3 Document the deployment contract: what deploy.sh receives (digest, environment), what it guarantees, how it is verified 1 day Anybody on the team can deploy in an emergency without the pipeline; it is also the plan B from 03-05 Medium
4 A record of pipeline decisions: why OIDC, why Environments, why this cache 0.5 days Faster onboarding and reviews; stops the same thing being reopened every quarter Medium
5 A periodic test: run make ci on a clean machine with no GitHub Actions 0.5 days Verifies that the portability is real and not theoretical; detects hidden dependencies on the runner's environment High
An abstraction layer over triggers, environments and permissions Weeks None Low

Where to stop: after point 5. The last row is the boundary, and the criterion is explicit: an abstraction layer over what is tool-specific costs more than the migration it would avoid and adds permanent indirection that harms readability — exactly what 04-05 warned about regarding extracting premature abstractions.

Before and after:

Today After the plan (7 days of work)
Logic in the YAML ~55 % ~10 %
Third-party actions 11 4, pinned by SHA and justified
Reproducible locally Partially Yes, verified periodically
Hypothetical migration 3-4 weeks 5-8 days

The line for Marta, which is what to say in the meeting: "Seven days of work. The migration would go from a month to a week, but that is the side effect: what we are really buying is that the team can run the pipeline on their laptop, that the logic gets reviewed as code and that the ci.yml is understood at a glance. If we never migrate, it is still worth it. And we do not go further than that: building an abstraction layer over GitHub Actions would cost more than the migration it would avoid."

Conclusion and close of the module

The module's conclusion rests on an empirical check you have carried out six times: the same Reservalia pipeline fits in every tool, because the problem is the same everywhere. Install reproducibly, verify in parallel with fast feedback, build an immutable artifact, scan it, publish it identified by its content, promote it by digest through a gate and be able to go back. What changes is the syntax, the names of the pieces and what the platform gives you for free. That is why the equivalence table in section 3 is worth more than any tutorial on a specific tool: it lets you read a .gitlab-ci.yml or a Jenkinsfile on your first day in a new job.

What each tool taught, and what you take away even if you never touch it again: Jenkins, that total control is paid for with real operational work and that a general-purpose engine integrates anything in exchange for bringing nothing solved. GitLab, that an integrated platform gives you traceability, credentials and environments for free, and charges for it in coupling. CircleCI, that a pipeline's speed is an engineering problem with concrete solutions — splitting by timings, explicit cache, resource classes — and that separating workspace, cache and artifacts clears up a confusion that lasts for years. Travis, that the fixed-phase model is enough for CI and not for CD, that your provider's business can change under your feet, and that a secret entrusted to a third party is a secret whose security you do not control. Docker and Kubernetes, that the substrate matters as much as the orchestrator, that the digest beats the tag, and that complexity is justified by a problem and not by who else uses it. And GitHub Actions, that the tool you use every day also has limits, dangerous asymmetries and a supply surface that has to be watched.

The criteria, in the order that matters: non-negotiable constraints filter first; where the code and the identity live decide 80 % of what is left; who is going to operate it rules options out; hardware adds hybrid pieces; budget and its shape fine-tune; and the team's maturity decides how much rope to give yourself. Technical quality does not appear, because by that point it is already decided. And the cost almost nobody calculates — people's hours — is the one that usually reverses the spreadsheet's result.

That closes module 6 and, with it, the conceptual part of the course. You have covered the principles (modules 1 to 4), four real contexts (module 5) and the machinery (module 6). What is left is for you to do it. Module 7 is entirely practical: five guided exercises that build, step by step, an end-to-end pipeline — the basic pipeline, the automated tests, the production deployment, the monitoring and feedback, and the hardening with security and secrets — plus a final project where you assemble your own complete one, with the gates, the immutable artifact, the deployment with rollback and the metrics to know whether it is working. That is where everything above stops being reading. Start with Exercise 1: Basic Pipeline, which begins exactly where 02-02 began, except this time you write it.

CI/CD Course: Continuous Integration and Deployment

Module 1: Introduction to CI/CD

Module 2: Continuous Integration (CI)

Module 3: Continuous Deployment (CD)

Module 4: Advanced CI/CD Practices

Module 5: Implementing CI/CD in Real Projects

Module 6: Tools and Technologies

Module 7: Practical Exercises

Module 8: Additional Resources

© Copyright 2026. All rights reserved