When somebody starts out in CI/CD, the biggest obstacle is usually not the concept: it is the noise. Jenkins, GitHub Actions, GitLab CI, CircleCI, Travis CI, Azure DevOps, Argo CD, Tekton, Spinnaker, Drone, Buildkite... the list seems endless and gives the impression that you have to choose well or lose months. This lesson is a map, not a tutorial. Its goal is for you to be able to place each tool in its box, understand which categories exist and why, and discover something very reassuring: 80% of what you learn in one tool transfers to the others, because they all implement the same concepts we saw in 01-01. At the end we will justify why the course uses GitHub Actions and we will see the same trivial job written in two different tools, so you can see for yourself that what changes is the syntax, not the idea.

Contents

  1. How the ecosystem is organised
  2. Category 1: CI/CD servers (the heart of the pipeline)
  3. Category 2: container orchestrators
  4. Category 3: artifact registries
  5. Category 4: infrastructure as code
  6. Category 5: GitOps continuous deployment
  7. The tools, one by one
  8. High-level comparison table
  9. Why this course uses GitHub Actions
  10. The same job in two tools
  11. Common mistakes and tips
  12. Exercises
  13. Conclusion

  1. How the ecosystem is organised

The first mistake when looking at this landscape is to lump every tool together and compare them with one another. Argo CD does not compete with Jenkins, just as a screwdriver does not compete with a toolbox: they do different things at different moments in the process.

The useful way of organising the ecosystem is by which part of a change's journey each tool covers:

flowchart LR
    A["📝 Code<br/>in the repository"] --> B["🏭 CI/CD SERVER<br/>builds and tests"]
    B --> C["📦 ARTIFACT<br/>REGISTRY<br/>stores the result"]
    C --> D["🚀 DEPLOYMENT<br/>takes the artifact<br/>to the environment"]
    D --> E["☸️ ORCHESTRATOR<br/>runs and keeps<br/>the service alive"]

    F["🏗️ IaC<br/>creates and governs<br/>the infrastructure"] -.-> E
    G["🔄 GitOps<br/>syncs the desired<br/>state from the repo"] -.-> D

    style B fill:#cfe8ff
    style C fill:#d9f2d9
    style D fill:#fff2cc
    style E fill:#f0d9ff

Five categories, five different functions:

Category What it does Examples
CI/CD servers Run the pipeline: react to repository events, build, test and launch deployments GitHub Actions, GitLab CI/CD, Jenkins, CircleCI, Travis CI, Azure DevOps Pipelines, Tekton
Container orchestrators Run the services and keep them alive: restarts, scaling, health Kubernetes, Amazon ECS, Nomad
Artifact registries Store what gets built in a versioned, immutable way Amazon ECR, Docker Hub, GitHub Packages, Artifactory, Nexus
Infrastructure as code (IaC) Define infrastructure in versioned files instead of by clicking around Terraform, OpenTofu, Pulumi, AWS CloudFormation, Ansible
GitOps continuous deployment Watch a repository and continuously sync the cluster with what it says Argo CD, Flux

A real project uses one from each category, not a single tool. By the end of the course, Reservalia will use: GitHub Actions (CI/CD server) + Amazon ECR (registry) + ECS Fargate (orchestrator) + Terraform (IaC). None of that is redundant.

  1. Category 1: CI/CD servers (the heart of the pipeline)

This is the category that takes up most of this course, and the one that causes most confusion. Within it, the most important structural distinction is hosted (SaaS) versus self-managed.

2.1. Hosted (SaaS) versus self-managed

Aspect Hosted / SaaS Self-managed (self-hosted)
Who maintains the server The provider You
Getting started Minutes: one YAML file in the repository Days: install, secure, configure agents
Cost Per minute of execution (+ free allowance) Machines + administration time
Updates and patches Automatic Your responsibility
Access to private networks Limited; requires tunnels or your own runners Native: the machine is already on your network
Control and customisation Lower: you play with the provider's pieces Total: any plugin, any operating system
Data and compliance Your code passes through third-party infrastructure Everything stays in your infrastructure
Examples GitHub Actions, CircleCI, Travis CI, GitLab.com Jenkins, self-hosted GitLab, Tekton, your own runners

There is a widely used middle ground: a hosted platform with your own runners. That is, GitHub Actions coordinates and provides the interface, but the jobs run on your machines inside your private network. It combines the convenience of SaaS with access to internal systems and is usually the choice of medium-sized companies with network or compliance requirements.

2.2. Where the pipeline is defined

Another distinction that heavily shapes the day-to-day experience:

  • In the repository (pipeline as code). The pipeline is a YAML file versioned alongside the code: it is reviewed in pull requests, it can be rolled back, it travels with the branches. This is the model of GitHub Actions, GitLab CI, CircleCI, Travis CI, Tekton and modern Jenkins with a Jenkinsfile.
  • In the server's interface (click-configured). The pipeline is configured by filling in web forms and lives in the server's database. This is the classic Jenkins model with freestyle jobs, and it is also possible in Azure DevOps.

The second option is a well-documented anti-pattern: the configuration is not versioned, nobody knows who changed what, it cannot be reproduced, and recovering the server after a disaster is a nightmare. Rule of thumb: if your pipeline is not in the repository, it is not under control. We will dig into this in lesson 04-05.

  1. Category 2: container orchestrators

A CI/CD server builds and launches the deployment, but it does not keep your application alive. That is the orchestrator's job: it decides which machine each container runs on, restarts it if it dies, scales it if there is load, checks its health and swaps versions without interrupting service.

Tool Model When it fits
Kubernetes The de facto standard, extremely powerful and extremely complex Many services, several teams, a need for portability across clouds
Amazon ECS / Fargate AWS's managed orchestrator; with Fargate you do not even manage servers You are on AWS and want containers without administering a cluster
HashiCorp Nomad Simpler than Kubernetes, orchestrates containers and also plain processes Mixed workloads, teams that want less complexity

Reservalia will use ECS Fargate. It is a deliberate and realistic decision for a three-person team: Kubernetes for three services and a part-time SRE is over-engineering. Kubernetes appears in lesson 06-05, and the microservices case in 05-03.

  1. Category 3: artifact registries

Remember the golden rule from 01-01: build once, promote the same artifact. For that to be possible, the artifact has to live somewhere every environment can retrieve it from. That somewhere is the registry.

Registry What it stores Notes
Amazon ECR Container images Integrated with IAM and ECS; the one we will use in Reservalia
Docker Hub Container images Public and very well known; download limits on free accounts
GitHub Packages Images and packages (npm, Maven, NuGet...) Attached to the repository; convenient if you already use GitHub
JFrog Artifactory / Sonatype Nexus Almost any format The enterprise option; often used as a dependency proxy as well

A critical property of a well-used registry: artifacts are immutable. The tag reservalia/api:a3f9c21 must always point at the same bytes. Re-tagging or overwriting destroys traceability and makes it impossible to know what is really in production. That is why we tag with the commit SHA and not with latest.

  1. Category 4: infrastructure as code

Nuria, Reservalia's SRE, created the RDS database, the load balancer and the network rules by hand in the AWS console. That is quick... once. The problem shows up when an identical staging environment has to be created, or when somebody changes something on a Tuesday and by October nobody remembers why.

Infrastructure as code defines those resources in versioned files:

# An illustrative Terraform fragment: Reservalia's database.
# You do not need to understand it all now; just see that the infrastructure
# is declared in a file that lives in the repository and is reviewed in a PR.
resource "aws_db_instance" "reservalia" {
  identifier     = "reservalia-${var.environment}"   # reservalia-staging, reservalia-prod
  engine         = "postgres"
  engine_version = "16.3"
  instance_class = var.environment == "prod" ? "db.t4g.medium" : "db.t4g.micro"
  multi_az       = var.environment == "prod"
}

What matters for this lesson is the category: these tools do not run pipelines, they define infrastructure. The pipeline invokes them. Terraform, OpenTofu, Pulumi, CloudFormation and Ansible play here. We will develop this in lesson 03-03.

  1. Category 5: GitOps continuous deployment

This is the most recent category and the least well understood, so the mechanism deserves an explanation.

Traditional CI/CD servers use a push model: the pipeline finishes and pushes the deployment to the environment. For that, the pipeline needs credentials with permission to modify production.

GitOps reverses the direction. It is a pull model: an agent installed inside the cluster continuously watches a Git repository that describes the desired state. When it detects a difference between what the repository says and what is in the cluster, it corrects it itself.

graph TB
    subgraph PUSH["PUSH model - traditional CI/CD"]
        P1["CI/CD pipeline"] -->|"production credentials<br/>+ pushes the change"| P2["Cluster"]
    end
    subgraph PULL["PULL model - GitOps"]
        G1["Git repository<br/>= desired state"] -.->|"the agent polls it"| G2["GitOps agent<br/>INSIDE the cluster"]
        G2 -->|"applies and corrects<br/>drift"| G3["Cluster"]
    end
    style PUSH fill:#fff6f6
    style PULL fill:#f6fff6

Advantages of the pull model: the pipeline never needs production credentials (an important security improvement), the repository is the auditable source of truth, and manual drift is reverted automatically. Drawback: it adds one more piece and is currently very tied to Kubernetes.

Tools: Argo CD (a very visual web interface, widely adopted) and Flux (more minimalist, with no interface of its own by default).

  1. The tools, one by one

Now for the individual introductions. For each one: execution model, where the pipeline is defined and SaaS or self-hosted.

7.1. GitHub Actions

  • Execution model: workflows triggered by repository events (push, pull_request, tags, cron, manual dispatch). Each workflow contains jobs; each job runs on an ephemeral runner (a clean virtual machine destroyed when it finishes).
  • Where it is defined: YAML files in .github/workflows/ inside the repository itself.
  • SaaS or self-hosted: SaaS, with the option of self-hosted runners in your own infrastructure.
  • Distinctive trait: the Actions Marketplace, an enormous catalogue of reusable steps (actions/checkout, actions/setup-node, aws-actions/configure-aws-credentials...). It hugely reduces the code you write, but it introduces third-party dependencies you have to pin and audit (lesson 04-03).
  • Strength: total proximity to the repository. The event, the code, the review and the result are all in the same place.
  • Weakness: the reuse model (reusable workflows and composite actions) is less flexible than a real programming language, and very complex pipelines can become verbose.

7.2. GitLab CI/CD

  • Execution model: a pipeline file with stages that group jobs; each job is picked up by a runner (with different executors: Docker, shell, Kubernetes).
  • Where it is defined: .gitlab-ci.yml at the root of the repository.
  • SaaS or self-hosted: both. GitLab.com is SaaS; self-managed GitLab is one of the most complete self-hosted options on the market.
  • Distinctive trait: it is an integrated DevOps platform: repository, CI/CD, container registry, issue tracking, security scanning and environments, all in the same product and with the same authentication.
  • Strength: vertical integration and a very powerful template model (include, extends) for reusing configuration across projects.
  • Weakness: if you self-manage GitLab, you take on a large piece of infrastructure.

7.3. Jenkins

  • Execution model: a controller server that orchestrates and a set of agents that run the jobs. The classic persistent-server model.
  • Where it is defined: ideally in a Jenkinsfile in the repository, written in a Groovy-based DSL (declarative or scripted). Historically also through forms in the web interface, which produces the technical debt we mentioned.
  • SaaS or self-hosted: self-hosted, always. It is open source software that you install yourself.
  • Distinctive trait: an ecosystem of more than a thousand plugins. It can integrate with literally anything. That is also its curse: plugins vary enormously in quality and maintenance pace, and version incompatibilities are a classic.
  • Strength: absolute control, more than two decades of maturity, and because the Jenkinsfile is real code it allows complex logic that would be impossible in YAML.
  • Weakness: high administration cost (updates, security, agent management) and an interface that shows its age. Even so, it remains dominant in large companies and in environments with internal systems that will never move to the cloud.
  • Lesson 06-01.

7.4. CircleCI

  • Execution model: jobs that run in Docker containers or virtual machines, with native support for parallelisation and automatic splitting of the test suite.
  • Where it is defined: .circleci/config.yml in the repository.
  • SaaS or self-hosted: mainly SaaS; it offers self-hosted runners and a server edition.
  • Distinctive trait: orbs, reusable configuration packages (the conceptual equivalent of GitHub actions), and a very pronounced focus on performance and pipeline execution time.
  • Strength: speed, good caching and parallelism tooling, agnostic about your repository provider.
  • Weakness: one more piece outside your repository platform, with its own billing and its own access management.
  • Lesson 06-03.

7.5. Travis CI

  • Execution model: builds triggered by repository events, run on virtual machines or containers.
  • Where it is defined: .travis.yml at the root of the repository.
  • SaaS or self-hosted: SaaS (with an enterprise edition).
  • Distinctive trait: it is historically important: it was the first to popularise the "a YAML file in your repository triggers your CI" model and to offer it free to open source projects. Practically everything we take for granted today started there.
  • Current situation: after the change to its free tier for open source, much of the ecosystem migrated to GitHub Actions and its share shrank considerably. We will study it in 06-04 for two reasons: there are still projects using it and you need to be able to read it, and its configuration is the simplest of them all, which makes it excellent for understanding the essence of a pipeline.

7.6. Azure DevOps Pipelines

  • Execution model: pipelines with stages, jobs and steps, run on agents hosted by Microsoft or on your own.
  • Where it is defined: azure-pipelines.yml in the repository (or through the classic visual editor, which is the old model).
  • SaaS or self-hosted: SaaS (Azure DevOps Services) or self-hosted (Azure DevOps Server, formerly TFS).
  • Distinctive trait: it is part of a complete suite (Boards, Repos, Artifacts, Test Plans) that is very well established in organisations invested in the Microsoft ecosystem, with excellent support for .NET and Windows.
  • Strength: very elaborate approval environments and governance controls, useful in companies with formal release processes.
  • Weakness: two models coexist (classic visual and YAML) and the historical documentation mixes both, which confuses newcomers.

7.7. Argo CD

  • Execution model: it is not a CI server. It is a controller that lives inside a Kubernetes cluster and continuously reconciles the cluster's state with what is declared in a Git repository.
  • Where it is defined: Kubernetes manifests (or Helm/Kustomize) in a Git repository.
  • SaaS or self-hosted: self-hosted in your cluster (there are third-party managed offerings).
  • Distinctive trait: it is the reference implementation of GitOps. It combines with other tools, it does not replace them: GitHub Actions builds and publishes the artifact, updates the manifest, and Argo CD takes care of getting it into the cluster.
  • When it makes sense: when you already use Kubernetes. Without Kubernetes, as things stand today, it does not apply.

7.8. Tekton

  • Execution model: a Kubernetes-native CI/CD framework: each pipeline step is a container and each run is a Kubernetes resource (Task, Pipeline, PipelineRun).
  • Where it is defined: Kubernetes YAML manifests, versioned in the repository.
  • SaaS or self-hosted: self-hosted on Kubernetes; it is the foundation of several commercial products.
  • Distinctive trait: it is not a tool "to use", it is a foundation for building your own CI/CD platform. Very powerful and very low level.
  • When it makes sense: large organisations building an internal platform for many teams. For a three-person team it is clearly excessive.

  1. High-level comparison table

Tool Category SaaS / self-hosted Where it is defined Execution model It fits well when...
GitHub Actions CI/CD server SaaS (+ own runners) .github/workflows/*.yml Ephemeral runners per job Your code is on GitHub and you want to start today
GitLab CI/CD CI/CD server Both .gitlab-ci.yml Runners with several executors You want a single, integrated DevOps platform
Jenkins CI/CD server Self-hosted Jenkinsfile (or the web interface) Controller + persistent agents You need total control, internal networks or exotic integrations
CircleCI CI/CD server SaaS (+ own runners) .circleci/config.yml Containers/VMs with parallelism Pipeline time is your priority and you want independence from your repository host
Travis CI CI/CD server SaaS .travis.yml VMs/containers per build You maintain a project that already uses it; or you want the simplest possible example
Azure DevOps Pipelines CI/CD server Both azure-pipelines.yml Hosted or self-hosted agents Your organisation lives in the Microsoft ecosystem
Argo CD GitOps deployment Self-hosted (on Kubernetes) Manifests in Git Continuous reconciliation (pull) You already use Kubernetes and want auditable deployments with no credentials in CI
Tekton CI/CD server Self-hosted (on Kubernetes) Kubernetes CRDs Each step, a container You are building an internal CI/CD platform for many teams

A warning about this table: do not choose by counting green boxes. In practice, the criterion that weighs most is where your code lives and what your team knows how to maintain. We will systematise the formal selection criteria in lesson 06-07.

  1. Why this course uses GitHub Actions

This course's choice is GitHub Actions, and it is worth explaining why so you know which reasons transfer to your case and which do not:

  1. Proximity to the repository. The pipeline lives in the same place as the code. The event that triggers it (a pull request), the review, the result and the history are all on the same screen. For learning, this removes an enormous barrier: there is no second system to configure and no cross-system credentials.
  2. A declarative, readable model. A GitHub Actions workflow reads from top to bottom almost like prose. Being declarative, you describe what you want, not how to orchestrate it. This makes the course's examples easy to understand even if you have never seen the tool.
  3. Zero cost to get started. Public repositories get free execution, and private ones a generous monthly allowance. You can reproduce every example in the course without paying anything.
  4. Very wide adoption. It is today the tool you are most likely to encounter on a new project, and it appears constantly in job adverts.
  5. The concepts are universal. Everything you will learn — triggers, jobs, matrices, artifacts, caching, secrets, environments with approval, promotion — exists under another name in all the others. The next section proves it.

And, for the sake of honesty, the reasons why you might choose another one:

  • Your code is not on GitHub → GitLab CI or CircleCI fit better.
  • You need to reach internal systems that never leave your network → Jenkins or self-hosted runners.
  • Your organisation already has an established platform → learn that one; the knowledge transfers just the same.

Module 6 goes into each tool separately (Jenkins in 06-01, GitLab CI in 06-02, CircleCI in 06-03, Travis CI in 06-04, Docker and Kubernetes in 06-05, GitHub Actions in depth in 06-06) and in lesson 06-07 we will look at the formal criteria for choosing. In addition, in that module we will re-express the same Reservalia pipeline in each tool, which is the best way to confirm that what you have learned transfers.

  1. The same job in two tools

Let us see it right now, on a small scale. The job is deliberately trivial: install the dependencies and run the tests for apps/api when somebody pushes code.

Important: you do not need to understand every line. Here we only want you to compare the two columns and see that they say the same thing. The real construction of Reservalia's pipeline starts in lesson 02-02.

10.1. GitHub Actions

# File: .github/workflows/test-api.yml
name: API tests

# TRIGGER: which event fires this workflow.
on: [push]

jobs:
  # Internal name of the JOB. It will appear like this in the GitHub interface.
  test-api:
    # RUNNER: the machine it runs on. Ephemeral and clean.
    runs-on: ubuntu-latest

    # STEPS: the steps, in strict order.
    steps:
      # Step 1: download the repository code onto the runner.
      # Without this, the machine is empty. It is always the first step.
      - uses: actions/checkout@v4

      # Step 2: install Node 20. We pin the version so that the
      # build is reproducible (a concept from lesson 01-01).
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      # Step 3: install dependencies EXACTLY as the lock says.
      # working-directory places the command inside apps/api.
      - run: npm ci
        working-directory: apps/api

      # Step 4: run the tests. If the command returns an exit code
      # other than 0, the job fails and the pipeline turns red.
      - run: npm test
        working-directory: apps/api

10.2. GitLab CI

# File: .gitlab-ci.yml (at the root of the repository)

# STAGES: the pipeline stages, in order.
stages:
  - test

# Name of the JOB.
test-api:
  # Which stage it belongs to.
  stage: test

  # RUNNER: here it is expressed as the Docker image the job runs in.
  # It is equivalent to runs-on + setup-node in GitHub Actions, in a single line.
  image: node:20

  # No checkout step is needed: GitLab clones the repository
  # automatically before running the job.
  script:
    - cd apps/api
    - npm ci
    - npm test

  # TRIGGER: here it is declared per job, not globally.
  rules:
    - if: $CI_PIPELINE_SOURCE == "push"

10.3. What changes and what does not

Put the two side by side and you will see that the concepts are identical; only where each one is written changes:

Concept (lesson 01-01) GitHub Actions GitLab CI
Trigger on: [push], global to the workflow rules: inside each job
Job A key under jobs: A top-level key
Stage Implicit; ordered with needs: Explicit: stages: + stage:
Runner runs-on: ubuntu-latest image: node:20
Downloading the code An explicit actions/checkout@v4 step Automatic
Preparing the language An actions/setup-node@v4 step Choosing the right image
Running commands - run: (one step per command) script: (a list of commands)
How failure is detected Exit code ≠ 0 Exit code ≠ 0

The two philosophical differences that explain nearly everything else:

  • GitHub Actions bets on reusable actions from the Marketplace (setup-node, checkout), which encapsulate work. You write less, but you depend on third parties.
  • GitLab CI bets on Docker images as the unit of environment. More explicit and with less magic, but you have to choose the right image yourself.

And what does not change in any tool in the world:

  1. A repository event triggers the run.
  2. A clean machine is prepared with the code on it.
  3. Dependencies are installed reproducibly.
  4. Commands are run.
  5. If a command returns a non-zero exit code, the pipeline fails.

That last point is especially liberating: the universal unit of success or failure in CI/CD is a process's exit code. Every tool, without exception, relies on it.

# Check it in your own terminal.
# Every command leaves its exit code in the $? variable
# 0 = success; any other value = failure.

echo "hello"
echo $?        # → 0   ✅ the pipeline would carry on

ls /folder/that/does/not/exist
echo $?        # → 2   ❌ the pipeline would turn red here

# That is why `npm test` works the same in all 8 tools in this lesson:
# test frameworks return 0 if everything passes and ≠0 if anything fails.

Common Mistakes and Tips

Mistake 1: comparing tools from different categories. "Which is better, Jenkins or Argo CD?" is a badly framed question: one runs pipelines and the other syncs clusters. Before comparing, place each tool in its box on the map from section 1.

Mistake 2: choosing by popularity. The fact that Kubernetes is the standard does not mean your three-person team needs it. The best tool is the one your team can maintain without devoting a third of its time to it. Reservalia chooses ECS Fargate for exactly that reason.

Mistake 3: believing the choice is irreversible. As you have just seen, migrating a pipeline between tools is above all a syntax translation job. What is genuinely hard to migrate is what has been done badly: pipelines configured from a web interface, huge unreadable scripts or secrets embedded in the YAML. Invest in your pipeline being well built, not in choosing the perfect tool.

Mistake 4: configuring the pipeline from the graphical interface. It is tempting because it is quick. Six months later nobody knows who changed what, it cannot be reverted and recovering the server is impossible. Every pipeline must live in the repository.

Mistake 5: piling up tools you do not need. Adding Argo CD "because GitOps" to a project that does not even use Kubernetes adds complexity with no benefit. Every new tool is one more system to learn, maintain, update and debug at three in the morning.

Tip 1: learn one in depth rather than five superficially. Deep knowledge of one tool transfers; superficial knowledge of five does not. That is why this course builds everything in GitHub Actions and only translates at the end, in module 6.

Tip 2: use the "where does my code live" criterion. It is the most effective filter. GitHub → GitHub Actions. GitLab → GitLab CI. An internal Git server → Jenkins or self-hosted GitLab. This simple criterion gets it right in the vast majority of cases.

Tip 3: pin the versions of the actions and plugins you use. actions/checkout@v4 rather than actions/checkout@main. A third-party action is somebody else's code running with access to your repository. It is a real attack surface and we will pay attention to it in lesson 04-03.

Tip 4: when reading documentation, mentally translate it into the concepts from 01-01. When you come across a new term, ask yourself: is this a trigger, a job, a stage, a runner or an artifact? It is nearly always one of those five things under another name, and that translation turns any tool's documentation into something familiar.

Exercises

Exercise 1: classify tools by category

Place each tool in its category from the map in section 1 (CI/CD server, container orchestrator, artifact registry, IaC or GitOps deployment) and state in one sentence which problem it solves:

  1. Terraform
  2. Amazon ECR
  3. Flux
  4. Amazon ECS Fargate
  5. CircleCI
  6. Nexus
  7. Kubernetes
  8. Tekton

Exercise 2: translate a workflow

Translate the following GitHub Actions workflow into a .gitlab-ci.yml. Then add a concept-by-concept equivalence table.

name: Verify the web app
on: [push]
jobs:
  lint-and-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
        working-directory: apps/web
      - run: npm run lint
        working-directory: apps/web
      - run: npm run build
        working-directory: apps/web

Exercise 3: recommend tools in three scenarios

For each team, recommend a combination of tools (CI/CD server + registry + execution target, plus GitOps or IaC if appropriate) and justify each choice in one or two sentences. There is no single correct answer; the justification is what counts.

  1. Reservalia. Three people. Code on GitHub. They deploy to AWS with ECS Fargate. No previous CI/CD experience. A very tight budget.
  2. Banco Meridiano. A 40-person platform team. The code is on an internal Git server that cannot leave the corporate network by regulation. They deploy to an on-premises Kubernetes. They need recorded, auditable approvals for every production deployment.
  3. Ludika. A 15-person video game studio. Code on GitLab.com. They compile builds for Windows, macOS and Linux on every release. The artifacts weigh several gigabytes.

Solutions

Solution to Exercise 1

Tool Category Problem it solves
Terraform Infrastructure as code Defines cloud resources (databases, networks, load balancers) in versioned files, instead of creating them by hand in a web console
Amazon ECR Artifact registry Stores container images in a versioned, immutable way, so the same artifact can be promoted between environments
Flux GitOps continuous deployment Continuously syncs a Kubernetes cluster with the state declared in a Git repository, using a pull model
Amazon ECS Fargate Container orchestrator Runs containers and keeps them alive (restart, scaling, health) without you having to administer servers
CircleCI CI/CD server Runs the build, test and deployment pipeline in response to repository events
Nexus Artifact registry Stores artifacts in multiple formats and also acts as a caching proxy for external dependencies
Kubernetes Container orchestrator Schedules, scales and maintains containers across a cluster of machines
Tekton CI/CD server Runs pipelines natively on Kubernetes, as a foundation for building your own platform

Two observations worth having noticed:

  • Flux and Tekton are both "on Kubernetes" but they are not the same category. Tekton runs pipelines (builds and tests); Flux syncs the deployed state. They are used together, not instead of one another.
  • ECR and Nexus are in the same category even though one is an AWS managed service and the other an installable product: the category is defined by function, not by distribution model.

Solution to Exercise 2

# .gitlab-ci.yml
stages:
  - verify

lint-and-build:
  stage: verify
  image: node:20          # replaces runs-on + setup-node
  script:
    - cd apps/web
    - npm ci
    - npm run lint
    - npm run build
  rules:
    - if: $CI_PIPELINE_SOURCE == "push"

Equivalence table:

Concept GitHub Actions GitLab CI Comment
Pipeline name name: Verify the web app No direct equivalent; the name comes from the job or the file A cosmetic detail
Trigger on: [push] (global) rules: if $CI_PIPELINE_SOURCE == "push" (per job) GitHub declares it at the top; GitLab, per job
Job jobs.lint-and-build Top-level lint-and-build: The same concept
Stage Implicit stages: + stage: verify GitLab requires you to declare it; GitHub infers it from the order and from needs:
Runner / environment runs-on: ubuntu-latest image: node:20 GitLab defines the environment through the image
Downloading the code actions/checkout@v4 Automatic GitLab always clones before the job
Node version actions/setup-node@v4 Included in node:20 Two different ways of pinning the version
Working directory working-directory: per step cd apps/web once In GitLab the commands share a shell
Commands - run: one per step script: list In GitHub each step is shown separately in the interface

A subtle nuance worth understanding: in GitHub Actions each run is an independent shell, which is why working-directory has to be repeated in each step — a cd in one step does not affect the next. In GitLab, all the commands in script share the same shell, so a single cd is enough. This difference catches many people out when migrating in either direction.

Solution to Exercise 3

1. Reservalia

  • CI/CD server: GitHub Actions. The code is already on GitHub: no additional systems, no cross-system credentials and a free allowance that is plenty for a team of three. Practically zero start-up cost, which is decisive with no previous experience and no budget.
  • Registry: Amazon ECR. It is in the same cloud as the workloads, authentication is resolved with IAM and pulls within the same region do not generate billable egress traffic.
  • Execution: ECS Fargate. It is already their target. It is the right decision for three people: containers without administering a cluster. Kubernetes here would be over-engineering and would eat up Nuria's time.
  • IaC: Terraform, from module 3 onwards, so that staging and prod are reproducible and do not depend on what Nuria remembers clicking.
  • GitOps: no. They do not use Kubernetes; Argo CD adds nothing here, only complexity.

2. Banco Meridiano

  • CI/CD server: self-hosted Jenkins (or self-managed GitLab). This is the decisive criterion: the code cannot leave the network. Any SaaS is ruled out by regulation, not by preference. The team's size (40 people) justifies the administration cost, which at Reservalia would be prohibitive.
  • Registry: Artifactory or Nexus on the internal network, with the added function of proxying external dependencies: useful when internet access is restricted.
  • Execution: their existing on-premises Kubernetes.
  • GitOps: Argo CD, highly recommended. It fits perfectly here for two reasons: they already have Kubernetes, and the pull model removes the need for the CI system to hold production credentials, which is a weighty argument in front of an auditor. On top of that, the Git repository of manifests becomes the auditable record of what was deployed, when and approved by whom.
  • Approvals: mandatory manual gates before production. This is a clear case of continuous delivery rather than continuous deployment, and that gate is a regulatory requirement, not a shortcoming.

3. Ludika

  • CI/CD server: GitLab CI/CD. The code is on GitLab.com: the "where does my code live" criterion applies. On top of that, the package and container registry comes built into the same product.
  • Registry: GitLab Package Registry, with one important caveat: multi-gigabyte builds per platform per release eat storage very quickly. A retention policy has to be defined from day one (for example, keep every release build and only the last N development ones) or the bill gets out of hand.
  • Cross-platform execution: self-hosted runners. There is a constraint here that many people discover too late: compiling for macOS requires Apple hardware, by licence. They will have to use their own macOS runners or hosted ones from a specialist provider, and be aware that the per-minute cost is appreciably higher.
  • Additional consideration: with artifacts this heavy, upload and download time and cost dominate the pipeline. It is worth investing in dependency caching and incremental compilation before any other optimisation (the subject of lesson 04-04).
  • GitOps: not applicable. The product is not deployed to a cluster; it is distributed to end users.

Conclusion

This lesson has brought order to the noise of the ecosystem:

  • Tools are organised into five complementary categories: CI/CD servers, container orchestrators, artifact registries, infrastructure as code and GitOps deployment. They do not compete across categories: they combine.
  • Within CI/CD servers, the two questions that most define the experience are SaaS or self-hosted and where the pipeline is defined. And there is one answer that admits no nuance: the pipeline must live in the repository, never in the forms of a web interface.
  • We have placed GitHub Actions, GitLab CI/CD, Jenkins, CircleCI, Travis CI, Azure DevOps Pipelines, Argo CD and Tekton, each with its execution model and its natural territory.
  • The course will use GitHub Actions for its proximity to the repository, its readable declarative model and its zero cost to get started; module 6 goes deeper into each tool and re-expresses Reservalia's pipeline in several of them.
  • And most importantly: by comparing the same job in GitHub Actions and GitLab CI we have confirmed that what changes is the syntax, not the concepts. Trigger, job, stage, runner, steps and exit code are in all of them. What you learn here transfers.

We now know what CI/CD is, why it is worth it and what it is built with. What is missing is the most concrete part: what exactly we are going to automate. In the next lesson, The Course Project: the Application We Are Going to Automate, we will get to know Reservalia in depth: its product, its team, the real structure of the repository, the scripts the pipeline will invoke, its three environments and the target AWS infrastructure. We will also see the roadmap of what we will have automated by the end of each module and what you need installed to follow the examples, whether you use Node.js or not.

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