Module 7: Gitops Beyond Terraform
2. GitLab CI, CircleCI, and Jenkins by contrast
Description
In Module 1, lesson 5, you met GitLab CI, CircleCI, and Jenkins in a conceptual table —who maintains them, where the config lives, the hosting model— and the market evidence that honestly motivated this guide's choice of GitHub Actions. This lesson goes one step further: it shows you Andes Cargo's same pipeline —fmt/validate/plan on every change, apply on every merge to main— written in the other three tools' real syntax, verified against their official documentation. The goal isn't for you to learn to write .gitlab-ci.yml or a Jenkinsfile from memory —that's another guide's content, if you ever need it—, it's for you to confirm with your own eyes something you've only read in prose until now: the pattern is identical across all four tools. The YAML keys' names change, whether the file is YAML or Groovy changes, how you filter by branch changes — but the pipeline's shape (verify, plan, review, apply) is the same.
Connection to the module
This is the lesson that opens the module, right after lesson 1's map. It's, on purpose, the "closest to home" comparison of the four this module brings: GitLab CI, CircleCI, and Jenkins solve the same problem as ci.yml and apply.yml —CI/CD on a code repository, with review before applying—, just with a different tool. Lessons 3 through 6 progressively move away from that known ground, toward problems neither Terraform nor GitHub Actions solve in this guide. No YAML in this lesson runs — there's no act for GitLab CI, CircleCI, or Jenkins—, so every block is explicitly labeled as named syntax, not executed.
The market evidence, revisited in full
Before getting into the syntax, it's worth bringing back, unedited, the exact quote you already saw in Module 1, lesson 5 — because this lesson is, literally, the one that lesson promised:
"CookUnity 'GitHub and GitHub Actions'; EarnIn 'GitHub Actions, Argo CD'; against the real Spanish stack: IRIUM 'Git, Jenkins, Artifactory, SonarQube', Apptiva 'Jenkins administration expertise', MediaStream 'Jenkins, TeamCity' — Jenkins shows up in 5 of 13, almost double GitHub Actions (~3)."
Jenkins shows up in 5 of 13 job postings from the Spanish stack this ecosystem's audit surveyed (src/paths/aws-cloud-ecosystem/VALIDACION.md, July 2026) — almost double GitHub Actions. This lesson doesn't repeat that evidence to insist on the same point twice: it brings it back because it's, exactly, the reason "learning GitHub Actions" can't be the end of the road if your goal is being prepared for the real Spanish-speaking job market. What you're going to see below —the same pipeline, in Jenkins syntax— is the concrete answer to that evidence: not a complete Jenkins course, but confirmation that the knowledge you already have translates, with low effort, to the tool that's most requested.
Analogy: the same paperwork, four different offices
Imagine you need to renew an ID document, and there are four different offices where you can do it — each with its own form, its own counter number, its own hours. Office A's form has a field called "Date of birth"; office B's calls it "Born on"; office C asks for the same data in separate day/month/year fields. The forms look different, the paper is a different color, even the process itself varies a bit in step order — but all four offices are asking you, underneath, for the same information, for the same purpose: confirming who you are and updating a record. Someone who's already done the process once, at any of the four offices, knows what information they're going to need to gather before walking in — the rest is learning where each field sits on that specific form.
That's, precisely, what you're about to see: four "forms" (GitHub Actions YAML, GitLab CI YAML, CircleCI YAML, Jenkins Groovy) for the same paperwork (verify an infrastructure change, show it for review, apply it automatically once approved).
The reference pipeline: Andes Cargo's ci.yml, in four syntaxes
You're going to see the same conceptual pipeline —ci.yml's five steps (Module 3) simplified down to three so the comparison stays readable: fmt (check style), plan (calculate the change), and triggering apply on merge to main— translated to each tool. None of the following three blocks ran — they're real syntax, verified against each tool's official documentation, shown so you recognize the shape, not so you copy them and expect them to work with no adjustments.
GitHub Actions (the one you already know, as a starting point)
name: ci
on:
pull_request:
branches: [main]
jobs:
terraform-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: terraform fmt -check -recursive
- run: terraform plan -input=false
GitLab CI — .gitlab-ci.yml (named, not executed)
stages:
- verify
- plan
terraform-fmt:
stage: verify
script:
- terraform fmt -check -recursive
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
terraform-plan:
stage: plan
script:
- terraform plan -input=false
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
The structure lives in the same repository, just like GitHub Actions — the biggest visible difference is stages/stage (GitLab CI groups jobs into explicitly named, ordered phases) versus jobs (GitHub Actions orders with needs, with no central list of phases), and rules/if instead of on/pull_request to decide when each job runs. $CI_PIPELINE_SOURCE is a GitLab CI predefined variable —the equivalent of the github.event_name you already used in Module 2— that tells you what triggered this run.
CircleCI — .circleci/config.yml (named, not executed)
version: 2.1
jobs:
terraform-checks:
docker:
- image: hashicorp/terraform:1.15
steps:
- checkout
- run: terraform fmt -check -recursive
- run: terraform plan -input=false
workflows:
ci:
jobs:
- terraform-checks:
filters:
branches:
ignore: main
CircleCI separates jobs (what work exists) from workflows (when and in what order that work runs) more explicitly than GitHub Actions, where both live together in the same file under jobs:. docker: with a specific image replaces the runs-on: ubuntu-latest + uses: hashicorp/setup-terraform@v3 you used in Module 3 — instead of an Action that installs Terraform on a generic image, CircleCI runs the job directly inside an image that already ships with Terraform installed. checkout is a reserved step, just as direct as actions/checkout@v4, but with no need to reference an external Action by version.
Jenkins — Jenkinsfile (named, not executed)
pipeline {
agent any
stages {
stage('Terraform fmt') {
when { changeRequest() }
steps {
sh 'terraform fmt -check -recursive'
}
}
stage('Terraform plan') {
when { changeRequest() }
steps {
sh 'terraform plan -input=false'
}
}
}
}
This is the biggest visible difference of the four: it's not YAML, it's Groovy (a real programming language, with its own { } block syntax), which gives Jenkins greater expressiveness —you can write complex conditional logic, functions, variables— at the cost of a steeper learning curve than declarative YAML. pipeline { agent any stages { ... } } is the mandatory minimal structure of any declarative Jenkins pipeline (there's also a "scripted" mode, older and freer, outside this lesson's scope). when { changeRequest() } is Jenkins's equivalent of "run only on a Pull Request" — the name comes from Jenkins, when integrating with GitHub or GitLab, calling the generic equivalent of a PR/MR a change request.
What does NOT change across the four (the table that matters)
| Concept | GitHub Actions | GitLab CI | CircleCI | Jenkins |
|---|---|---|---|---|
| Config file | .github/workflows/*.yml | .gitlab-ci.yml | .circleci/config.yml | Jenkinsfile |
| Language | YAML | YAML | YAML | Groovy (own DSL) |
| Grouping steps | jobs → steps | stages → jobs per stage | jobs → steps, orchestrated in workflows | stages → stage → steps |
| Conditioning on event/branch | on: + branches: | rules: + if: | filters: inside workflows | when { } inside a stage |
| Bringing the code to the runner | uses: actions/checkout@v4 | Automatic (implicit in every job) | checkout (reserved step) | Automatic if the Jenkinsfile lives in the repo (Declarative: Checkout SCM) |
| Installing a tool | uses: hashicorp/setup-terraform@v3 | image: with the tool preinstalled, or before_script | docker: with an image that includes it | sh 'apt-get install ...' or a preconfigured tool on the server |
Four rows in that table say, underneath, the same thing with a different name: "what work needs to happen, in what order, and under what condition does it run." None of the four tools invented a concept the other three don't have — what varies is the exact keyword and whether that keyword lives in declarative YAML or a complete programming language (Jenkins's case).
apply on merge: the same trigger, four ways to name it
The apply.yml pattern (Module 5) —automatically running terraform apply when a change reaches main— translates directly too:
| Tool | How apply triggers on merge (named) |
|---|---|
| GitHub Actions | on: push: branches: [main] in a separate workflow (apply.yml), exactly what you built in Module 5 |
| GitLab CI | A job in the same .gitlab-ci.yml, with rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' |
| CircleCI | A job in the same workflows, with filters: branches: only: main |
| Jenkins | A stage with when { branch 'main' } in the same Jenkinsfile, or a separate Jenkins job triggered by a merge webhook |
Notice a real design detail: GitHub Actions and GitLab CI/CircleCI/Jenkins differ in whether they split CI and CD into separate files (this guide's approach, with ci.yml and apply.yml as two independent workflows) or combine them into a single file with different conditions per job (the more common pattern in GitLab CI, CircleCI, and Jenkins, where stages/workflows already group the whole lifecycle). Neither approach is "more correct" — this guide chose to split the files because it makes reasoning about "what triggers what" simpler while you're learning (Module 5, lesson 2), but a real GitLab CI pipeline that combines everything into one file, with well-thought-out rules, achieves exactly the same security and ordering result.
Common mistakes
Copying one of this lesson's YAML files expecting it to work as-is (the most direct one). What happens: someone copies this lesson's .gitlab-ci.yml or .circleci/config.yml into a real project and is surprised when it fails. Why it happens: the rest of this guide got you used to every code block having "really run" — this lesson breaks that pattern on purpose, and says so, but it's easy to miss. How to spot it: if you tried running any of this lesson's three GitLab CI/CircleCI/Jenkins YAML files outside this lesson. How to fix it: reread each block's header — it explicitly says "named, not executed." They're simplified examples showing the pipeline's shape, not tested artifacts like Module 3's real ci.yml.
Believing Jenkins is "worse" for using Groovy instead of YAML (value-judgment-based). What happens: someone, used to simple declarative YAML, sees the Jenkinsfile with its { } syntax and concludes Jenkins is an unnecessarily complicated or outdated tool. Why it happens: YAML feels, at first glance, simpler to read than a complete programming language. How to spot it: if your takeaway from this lesson is "Jenkins is worse" instead of "Jenkins is more expressive, at the cost of a different learning curve." How to fix it: Groovy gives Jenkins a capability pure declarative YAML doesn't easily have — complex conditional logic, reusable functions, loops — precisely the flexibility that explains why teams with very large, very old pipelines (recall the market evidence: Jenkins dominates the Spanish stack) keep investing in maintaining it, instead of migrating.
Thinking "the pattern transfers" means "you don't need to learn the new syntax" (overconfidence-based). What happens: someone, after seeing this comparison, assumes they could write a production Jenkinsfile without studying Jenkins's documentation first, just because "they already understand the pattern." Why it happens: recognizing a pipeline's general shape (verify, plan, review, apply) feels similar to knowing a specific tool's exact syntax. How to spot it: if you think this lesson prepared you to write production Jenkins/GitLab CI/CircleCI, instead of to recognize its shape and know what to look for first in its documentation. How to fix it: what transfers is the mental vocabulary —"this is the verify step," "this is the branch condition," "this is where the tool gets installed"— not each tool's exact syntax. Learning any of these three tools' real syntax would still be real work, just with a much shorter curve than starting from zero.
Exercises
Exercise 1 — Translate a GitHub Actions key to the other three. Without looking at this lesson's tables, write on: pull_request: branches: [main]'s equivalent in GitLab CI, CircleCI, and Jenkins — it doesn't need to be syntactically perfect, but it should use each tool's correct concept name.
See solution
GitLab CI: rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' (inside a job). CircleCI: filters: inside the workflows block, typically on the target branch instead of the event (CircleCI doesn't distinguish "PR" as an event the same way GitHub does — it runs on every push to a branch that opened a PR). Jenkins: when { changeRequest() } inside a stage. If you wrote the correct key name in all three tools (rules, filters, when), you have the concept's translation clear, even though each one's exact syntax has its own details only their official documentation covers in full.
Exercise 2 — Identify the biggest structural difference among the four. Of this lesson's four tools, which one has the most fundamental difference from the other three, and why?
See solution
Jenkins, for two combined reasons: it uses Groovy, a complete programming language, instead of declarative YAML (which GitHub Actions, GitLab CI, and CircleCI share); and, in the vast majority of real cases, it runs on a self-hosted server the company itself maintains, while the other three are integrated SaaS (GitHub Actions, GitLab CI) or independent SaaS (CircleCI) with hosted runners by default. These two differences together explain why Jenkins has the steepest learning curve of the four, and also why it remains, according to this lesson's evidence, the tool with the greatest presence in the surveyed Spanish stack — the investment already made in Jenkins infrastructure and knowledge has a real replacement cost.
Exercise 3 — Explain the pattern to a technical recruiter. A recruiter reviewing your portfolio sees you only have experience with GitHub Actions and asks: "could you work with Jenkins if the role requires it?" Answer in two or three sentences, using this lesson's evidence and concrete content — not a vague "I learn fast" claim.
See solution
A complete answer sounds, roughly, like this: "The pipeline I built with GitHub Actions follows the same pattern almost any CI/CD tool uses: verify the change, calculate what it would modify, leave it for review, and apply it automatically once approved — that pattern is the same in Jenkins, only the syntax changes: Groovy instead of YAML, stage/when instead of jobs/on. I know, concretely, what to look for in Jenkins's documentation to write that same pipeline —the pipeline block, stages, when { branch }— even though I don't have real hands-on hours with the tool yet." The key to a good answer: naming the shared pattern precisely, without overstating experience you don't have with Jenkins's specific syntax.
Summary and next step
In this lesson you saw Andes Cargo's same pipeline —verify, plan, apply on merge— translated into GitLab CI's, CircleCI's, and Jenkins's real syntax, verified against each one's official documentation, though none of it really ran. You confirmed, with concrete examples instead of just prose, that the pattern transfers: the key's name changes (on/rules/filters/when), the language changes (YAML versus Groovy in Jenkins), but the pipeline's shape is the same across all four. You also revisited Module 1's complete market evidence —Jenkins in 5 of 13 Spanish-stack postings— now with the concrete syntax contrast that lesson promised.
Before moving on you should be able to: write from memory the conceptual equivalent of on/jobs/steps in the other three tools; explain why Jenkins is the most different of the four; and answer, with concrete evidence, whether your GitHub Actions knowledge transfers to a role that asks for Jenkins.
Lesson 3 moves away from known ground: you're going to meet ArgoCD and Flux, two tools that don't solve "the same problem with different syntax" — they solve GitOps with a genuinely different mechanism, designed specifically for Kubernetes.
Resources
- GitLab Docs — CI/CD YAML syntax reference — the complete reference for
stages,rules,script, and the rest of the keys used in this lesson. - CircleCI Docs — Configuration reference — the complete reference for
jobs,workflows,filters, anddockerused in this lesson. - Jenkins — Pipeline syntax — the complete reference for the declarative pipeline (
pipeline,stages,when) used in this lesson. - Jenkins — Documentation — general Jenkins documentation, the tool with the greatest presence in the Spanish stack according to this lesson's evidence.
src/paths/aws-cloud-ecosystem/VALIDACION.md(NIEVA, market audit, jul-2026) — the exact source for the Jenkins vs. GitHub Actions quote revisited at the start of this lesson.