The third outstanding front was stated in module 3 with an uncomfortable sentence: "the pipeline has permissions over production and has never been audited". It is literally true. cd.yml can deploy any image to prod, infra.yml can modify the AWS infrastructure, and both run third-party actions nobody on the team has read. If somebody compromises Reservalia's repository they do not need to attack production: the pipeline deploys it for them. And the previous lesson added the other side of the same problem: 1,147 packages from around 330 maintainers are downloaded, executed and end up inside the image. This lesson tackles both halves — the security of the software passing through the pipeline and the security of the pipeline itself — and finishes with the supply chain: SBOM, signing and provenance. With a prior warning worth taking seriously: this teaches you to automate controls, it does not replace a review by a security professional or your organisation's compliance judgement. A pipeline with six green scanners is not a secure system: it is a system with six green scanners.

Contents

  1. The pipeline as an attack surface
  2. The five automated analyses and what each one catches
  3. Reservalia's security job
  4. The severity policy: what breaks the build and what opens a ticket
  5. Secrets: where they live and why OIDC was a security decision
  6. When a secret leaks
  7. Least privilege in the pipeline's permissions
  8. Third-party code: pinning actions by SHA and the risk of pull_request_target
  9. Branch protection, environments and the audit log
  10. Supply chain: SBOM, signing and provenance
  11. Common Mistakes and Tips
  12. Exercises
  13. Conclusion

  1. The pipeline as an attack surface

For two modules we have treated the pipeline as a productivity tool. Seen from outside it is something else: a system with privileged access that runs third-party code and lends its reputation to whatever it produces.

Property of the pipeline Why an attacker cares
It holds prod credentials It is a path to production that goes through no firewall
It runs third-party code Actions, npm packages and their install scripts run with those permissions
It publishes the trusted artifacts What comes out of it is never questioned again
It reacts to events from strangers A pull request from a fork can trigger work
Its logs pass before many eyes A secret printed once stays in the history

From that come the two questions that structure the lesson. The first is does the software we ship have security flaws?, and it is answered by putting analysis inside the pipeline: that is shift-left, moving detection to the cheapest possible moment, the same fail fast argument from 04-01 applied to a different class of defect. The second is is the pipeline itself secure?, and it is answered with permissions, secrets and control of third-party code. A team that only does the first has pretty scanners protecting a system anybody can hijack.

  1. The five automated analyses and what each one catches

Type What it analyses What it catches When it runs False positives
SAST Your source code SQL injection, XSS, unsafe paths, misused cryptography On every PR Medium
SCA Your dependencies Versions with known vulnerabilities (CVEs) On every PR and daily Low, but a lot of irrelevant noise
Secret detection Code and git history Committed keys, tokens and passwords Every PR, and once over the whole history Low
Image scanning The built image Vulnerabilities in the base system and the layers After docker build High: many are not exploitable
DAST The running application Headers, authentication, exposed configuration Nightly, against staging Medium

The differences matter more than the names. SAST reads code without executing it: it finds the dangerous pattern but does not know whether that path is reachable, hence its false positives. SCA does not look at your code at all, it merely compares your dependency tree with a vulnerability database; it is the cheapest and the noisiest, because a CVE in a library you only use in tests is not a real risk. Secret detection is the only one that must look at the entire history: deleting a key in a later commit does not remove it from git, it is still one git log -p away. Image scanning finds dozens of base-system vulnerabilities that do not even have a patch available. And DAST is the only one that tests the real system, with its configuration and its headers; that is why it runs against staging and in the nightly pipeline: it is slow and needs something deployed.

None of them replaces the others, and none of them replaces thinking. A business logic vulnerability — "I can cancel another business's appointment by changing the identifier in the URL" — is caught by none of the five. That is covered by design, review and tests written on purpose.

  1. Reservalia's security job

Reservalia adds a fifth job to ci.yml, in parallel with the others so as not to lengthen the critical path:

  security:                                    # ~2 min · in parallel with quality/test/build
    name: Security
    runs-on: ubuntu-22.04
    timeout-minutes: 15
    permissions:
      contents: read
      security-events: write                   # 1 · upload results to the Security tab
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }               # 2 · gitleaks needs the history

      - name: Leaked secrets
        uses: gitleaks/gitleaks-action@v2      # 3
        env: { GITLEAKS_CONFIG: .gitleaks.toml }

      - uses: github/codeql-action/init@v3     # 4 · SAST
        with: { languages: javascript-typescript, config-file: .github/codeql/config.yml }
      - uses: github/codeql-action/analyze@v3

      - name: Vulnerable dependencies (SCA)
        run: npm audit --audit-level=high --omit=dev    # 5

      - name: Scan the image
        uses: aquasecurity/[email protected]          # 6
        with:
          image-ref: reservalia/api:${{ github.sha }}
          severity: 'CRITICAL,HIGH'
          ignore-unfixed: true                          # 7
          exit-code: '1'
          trivyignores: .trivyignore
  1. security-events: write lets the findings appear in the repository's Security tab rather than getting lost in a log. A finding that only exists in a job's output is a finding nobody will read twice.
  2. fetch-depth: 0 brings the full history. By default the checkout is shallow and gitleaks would only see the last commit, so a secret committed eight months ago would go unnoticed.
  3. gitleaks looks for credential patterns. In .gitleaks.toml you declare the justified exceptions — an example key in the documentation, a token in a fixture — with a comment explaining why they are not real.
  4. CodeQL is GitHub's SAST. It analyses the data flow between the program's inputs and dangerous operations, so it spots that a req.query parameter ends up concatenated into a SQL query even if it passes through three intermediate functions.
  5. npm audit with --audit-level=high only fails on the serious ones, and --omit=dev excludes development tooling: a denial of service in the test runner is not a production risk and, if it blocks the merge, the team will learn to work around the check.
  6. Trivy analyses the image the build job has just produced: base system packages, interpreter libraries and bundled dependencies. It is a different scan from npm audit because it covers what lies underneath your code.
  7. ignore-unfixed: true is the setting that makes the tool usable: it hides vulnerabilities with no patch available. Without it, the first run returns dozens of findings nothing can be done about, and the team's natural reaction is to stop looking.

The first run against Reservalia gives the usual result: 1 secret in the history — a staging DATABASE_URL committed in 2024 and "deleted" the next day — 3 high CodeQL findings, 9 dependency vulnerabilities of which 2 are high, and 31 from the base image, 26 of them unpatched. It is a perfectly normal picture, and the right reaction is not to block the pipeline until everything is fixed, but the one in the next section.

  1. The severity policy: what breaks the build and what opens a ticket

This is where most teams fail, always in the same way: everything is turned on as blocking, the pipeline goes red over things nobody can fix today, somebody adds continue-on-error: true "temporarily", and six months later the security job has been amber for half a year with nobody looking at it. An alert that gets ignored is worse than not having it, because it also gives a feeling of coverage.

Severity Effect Deadline Who
Secret detected Always blocks, no exceptions Immediate: rotate the credential Nuria
Critical with a patch available Blocks the merge Immediate The PR author
High with a patch, in new code Blocks the merge Immediate The PR author
High inherited (already in main) Informs and opens a ticket 30 days Dependency rota
Medium Informs 90 days or when that area is touched Dependency rota
Low or no patch available Recorded, no notification Quarterly review Marta

Three principles hold the table up. The distinction between new and inherited is the same as the SonarQube quality gate from 02-05: you cannot ask a twenty-line PR to resolve the previous 1,847 warnings, but you can ask it not to add a new one; with that, the debt stops growing and shrinks on its own as the code is touched. Secrets are the only category with no nuance: a committed credential is compromised even if the commit is deleted, so there is no "I will fix it next week". And every severity has an owner and a deadline: a finding with no owner is not a task, it is a notice.

A note on exceptions, because there will be some. When a vulnerability does not apply — the path is unreachable, the library is only used in a development script — it is documented with a reason and an expiry date:

# .trivyignore
# CVE-2024-XXXXX · openssl in the base system. Not reachable: the API opens no
# outbound TLS connections. Review on 2026-10-01 or when the base image changes.
CVE-2024-XXXXX

An exclusion with no date and no reason is indistinguishable from an oversight, and two years later nobody knows whether it still holds.

  1. Secrets: where they live and why OIDC was a security decision

In 03-02 we chose OIDC and Secrets Manager for operational convenience. Now it is time to justify them as what they really were.

Where Who can read it Rotation What happens if it leaks
Repository environment variable Anyone who can run a workflow Manual It works until somebody changes it
GitHub repository secret Any workflow in the repository Manual The same, but masked in the logs
Environment secret (prod) Only jobs with environment: prod Manual Limited to that environment
OIDC (no secret) Only whoever meets the role's condition Not applicable: there is no secret Nothing to leak
Secrets Manager Only the running task Without deploying It is audited in CloudTrail

The most surprising row is the first. A repository secret with no environment scope is available to any workflow in the repository, including one somebody adds in a pull request: a three-line workflow that prints the value base64-encoded is enough to take it — GitHub's masking hides the literal, not a transformation. That is why Reservalia's production secrets live only in the prod environment, which additionally requires human approval.

And that is why OIDC is qualitatively different: it is not "a better-kept secret", it is the absence of a secret. GitHub issues a short-lived token asserting who the workflow is, and AWS decides whether that assertion matches the role's condition. There is nothing to rotate, nothing to leak and nothing that expires badly. The rule that follows: prefer federated identity to credentials; if that is not possible, narrowly scoped secrets; and if not even that, at least rotate on a schedule. On rotation: a secret that is never rotated has total accumulated exposure — it passed through every machine and every person who touched the system since it existed. Reservalia rotates what remains quarterly (the payment gateway token, the SMS provider key) and rehearses the rotation just as it rehearses the rollback, because a rotation nobody has tested fails on exactly the day it has to be done in a hurry.

  1. When a secret leaks

This section matters because the order of the steps determines the damage, and the most common mistake is starting with the wrong one.

  1. Rotate the credential. First, before anything else. Generate a new one, deploy it, invalidate the old one. As long as the credential remains valid, everything else is cosmetic.
  2. Review what was done with it. CloudTrail, provider logs, anomalous access. Assume it was used, not that it was not.
  3. Afterwards, clean the git history if appropriate. It is the most laborious part — rewriting history breaks everybody's clones — and the least urgent, because the damage is already done.
  4. Record it as an incident, with a cause and a preventive measure.

The classic mistake is to do 3 first: delete the commit, breathe a sigh of relief and not rotate. It feels like having solved the problem while the key still works and is probably already in somebody's clone or in an automated index. The rule, no exceptions: a secret that has been in a repository is compromised, even if the repository is private and even if the commit has been deleted. At Reservalia, the staging DATABASE_URL from section 3 is handled like this: the RDS password is rotated that same day, CloudTrail is reviewed for connections from outside the VPC, and the decision is made not to rewrite the history — it was staging, the database was not reachable from the internet, and rewriting two years of history has its own cost — leaving it documented in SECURITY.md. The reasoned, written decision is part of the response.

  1. Least privilege in the pipeline's permissions

Every job should have exactly the permissions it needs and not one more, on two planes: those of the GitHub token and those of the AWS role.

# .github/workflows/ci.yml — header
permissions:
  contents: read              # 1 · the default for the WHOLE workflow

jobs:
  publish:
    permissions:
      contents: read
      id-token: write         # 2 · only this job can request the OIDC token
  1. Declaring permissions at workflow level is the first thing to do in an existing repository. An older organisation's default may well be write-all, which means a compromised script in any job can push to main, close issues or publish releases.
  2. id-token: write only where it is needed, because it is the permission that allows requesting the OIDC token and, with it, assuming AWS roles. A lint job does not need it.

On the AWS side the same principle governs the roles already created: AWS_ROLE_CI only needs to push to an ECR repository — not read secrets, not touch ECS; reservalia-deploy-prod needs to update the prod service and nothing in dev; and reservalia-terraform-plan is a read-only role, distinct from reservalia-terraform-apply, which is exactly what makes it safe to run the plan on every pull request.

The question that orders the whole section: if this job were compromised, how far would it get? If the answer is "all the way to production" and the job is the one running a stranger's PR tests, there is a design problem and not a configuration one. That is the deep reason ci.yml and cd.yml are two files: the privilege boundary from 04-01.

  1. Third-party code: pinning actions by SHA and the risk of pull_request_target

uses: actions/checkout@v4 means "run whatever happens to be on the v4 tag today". A git tag is movable: whoever controls the action's repository can repoint it at another commit, and your pipeline will run that code with its permissions, without a single line of your repository changing.

      # Fragile: the tag can be moved
      - uses: someone/util-action@v3

      # Robust: the SHA is immutable
      - uses: someone/util-action@8f4b7c2e1d09a6f35b2c0e7a91d4f8b6c3a2e5d1  # v3.2.1

The SHA is a cryptographic fingerprint of the content and cannot be repointed. The comment with the readable version is essential so that the Dependabot pull request updating it means something to a human, and with the github-actions entry in the dependabot.yml from 04-02 those SHAs are kept up to date automatically. Reservalia applies the rule in layers, which is the pragmatic approach: third-party actions, always by SHA; official GitHub and AWS actions, by major tag, consciously accepting the risk in exchange for less noise.

pull_request_target deserves its own paragraph because it is the most dangerous trap in GitHub Actions. The normal trigger, pull_request, runs the workflow without secrets when the PR comes from a fork: that is the safe default. pull_request_target exists for cases where a workflow needs secrets while processing external PRs — labelling, commenting — and it works by running the workflow from the base branch but with full access to the secrets. The flaw appears the moment somebody adds a checkout of the PR's code:

on: pull_request_target          # ← with secrets available
jobs:
  dangerous:
    steps:
      - uses: actions/checkout@v4
        with: { ref: '${{ github.event.pull_request.head.sha }}' }   # ← the attacker's code
      - run: npm ci                                                   # ← runs it with secrets

An npm ci runs the postinstall scripts from the PR's package.json, so anybody can open a pull request from a fork with a postinstall that sends every environment secret to a server of their own. Rule: do not combine pull_request_target with a checkout of the PR's code. If you need both, split them into two workflows: an unprivileged one that builds and saves an artifact, and a privileged one that only consumes that artifact without executing it. Reservalia does not use pull_request_target anywhere, which is the simplest answer.

  1. Branch protection, environments and the audit log

The rules from 02-07 — mandatory review, green checks, CODEOWNERS, no force-push, administrators included — stop being process hygiene and become security controls the moment you see the pipeline as a path to production: if main can be modified without review, everything else is pointless, because whoever modifies main also modifies the workflow that deploys. Three additional controls Reservalia now adds:

Control What it prevents Cost
Signing commits (GPG or SSH) Impersonating a commit's authorship One configuration per person
CODEOWNERS over .github/ A pipeline change being merged without Nuria seeing it None: it already existed
Approval for external contributors' PRs Running strangers' workflows without looking One click per PR

And a capability that is discovered late and is worth a great deal: the audit log. GitHub records who changed a secret, who approved a prod deployment, who modified a protection rule and who ran rollback.yml; AWS records in CloudTrail every API call with the identity that made it, including OIDC's AssumeRoleWithWebIdentity. It is worth looking at them before you need them, for two reasons: to know they exist and what they contain, and to discover in the cold things like a role you thought had been deleted still being used every night.

  1. Supply chain: SBOM, signing and provenance

The three questions a team must be able to answer about any artifact in production are: what it contains, who produced it and how it was produced. One mechanism for each.

SBOM (Software Bill of Materials): the complete list of an artifact's components in a standard format (SPDX or CycloneDX). It answers what it contains, and its real value shows up the day a serious vulnerability appears in a library: instead of investigating for hours, you consult the SBOM of every deployed version and in two minutes you know whether you are affected and since when.

# infra/scripts/generate-sbom.sh
syft "reservalia/api@${DIGEST}" -o spdx-json > "sbom-${SHA}.spdx.json"        # 1
cosign attach sbom --sbom "sbom-${SHA}.spdx.json" "reservalia/api@${DIGEST}"  # 2
  1. Syft inspects the already-built image and lists everything: npm packages, base system packages, versions and licences. It is generated against the artifact, not the source code, because what matters is what was actually deployed.
  2. It is attached to the artifact in the registry rather than filed away in a drawer: that way the SBOM travels with the image and is retrievable by digest years later.

Signing with cosign: answers who produced it. In keyless mode it uses the same OIDC from section 5, so there is no private key to guard either and the signer's identity is the workflow itself.

      - uses: sigstore/cosign-installer@v3
      - run: |
          cosign sign --yes "${ECR}/reservalia/api@${DIGEST}"
          cosign verify --certificate-identity-regexp '.*reservalia/reservalia.*' \
            --certificate-oidc-issuer https://token.actions.githubusercontent.com \
            "${ECR}/reservalia/api@${DIGEST}"

Signing without verifying is useless: the verification has to happen before deploying, inside cd.yml, so that an image appearing in ECR without a valid signature never reaches production. It is the cryptographic equivalent of the smoke test: checking that what you are about to deploy is what you think it is.

Provenance attestations: answer how it was produced. They are a signed document declaring which commit, which workflow and what moment the artifact was built from. With that the chain is closed: from the digest you reach the commit, from the commit the pull request, and from the pull request whoever reviewed it. SLSA is the framework that orders all of this into levels and serves as a road map:

Level What it requires Reservalia
1 An automated build that generates provenance ✅ since module 2
2 A hosted build service and signed provenance ✅ once cosign is added
3 An isolated, unforgeable build, verified source ⚠️ requires hardening permissions and isolating
4 Two-person review and a hermetic, reproducible build ❌ a distant goal

The sensible thing for a team like Reservalia's is to reach level 2 and deliberately stay there, leaving 3 documented as a goal. Moving up a level costs, and a half-achieved level nobody verifies contributes nothing.

Common Mistakes and Tips

Mistake 1: turning every scanner on in blocking mode on day one. The pipeline goes red over things with no solution, somebody adds continue-on-error and security disappears from the process without anybody deciding it. Mistake 2: not distinguishing new findings from inherited ones, which is the way to guarantee the debt never goes down.

Mistake 3: deleting the commit with the secret and not rotating the credential. The commit is the least of it: the key still works. Mistake 4: storing production secrets as repository secrets rather than environment secrets, which puts them within reach of any workflow, including one opened in a PR.

Mistake 5: using third-party actions by tag. A tag can be moved; a SHA cannot. Mistake 6: combining pull_request_target with a checkout of the PR's code, which is handing over the secrets to anyone who can write a postinstall.

Mistake 7: permissions: write-all inherited from the organisation, which lets a lint job push to main. Mistake 8: signing artifacts and not verifying the signature before deploying: a ceremony with no effect.

Tip 1: start in informative mode and block in phases, category by category, once the noise is under control. Tip 2: put a date and a reason on every exclusion, or within a year nobody will know whether it still holds. Tip 3: write a SECURITY.md with who to notify, what to do about a leaked secret and what is expected of an external contributor. Tip 4: ask for a professional review before any certification or contractual commitment; this course prepares you for that conversation, it does not replace it.

Exercises

Exercise 1

A team enables Trivy with the default configuration. The first run returns 214 vulnerabilities in the image: 3 critical, 18 high and the rest medium and low; 180 have no patch available. The pipeline is blocked and nobody can merge. Propose a concrete plan for the next four weeks.

Exercise 2

Review this workflow and list every security problem you find, ordered by severity, stating the fix for each:

on: pull_request_target
permissions: write-all
jobs:
  validate:
    steps:
      - uses: actions/checkout@v4
        with: { ref: '${{ github.event.pull_request.head.sha }}' }
      - uses: anyone/setup-tool@main
      - run: npm ci && npm test
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET }}

Exercise 3

A critical vulnerability appears in a very widely used library. Marta asks: "are we affected, since when and which versions do we have deployed?". Explain how it is answered with what was built in this lesson and the previous one, and what would be missing if Reservalia had not generated SBOMs.

Solutions

Solution 1. The initial mistake was enabling in blocking mode a tool whose noise level was unknown. A week-by-week plan:

Week 1 — stop blocking without stopping looking. Set the scan to informative (exit-code: '0'), enable ignore-unfixed: true and limit it to CRITICAL,HIGH. From 214 findings you go down to about 20 actionable ones: the 180 unpatched ones leave the daily noise and the mediums and lows are recorded for the quarterly review. The team can merge again, which is the precondition for fixing anything at all.

Week 2 — the highest-yield move. Update the base image to the latest patch version of node:20.11.0-bookworm-slim. A good part of the base system vulnerabilities disappear with that single line, without touching code. Rebuild and measure again.

Week 3 — the remaining criticals and highs, one by one. For each: is there a patch? Update. No patch but unreachable? Into .trivyignore with a reason and a date. No patch and reachable? A ticket with an owner and a provisional mitigation.

Week 4 — close the gate. Enable blocking only for criticals and highs with a patch available that are new relative to main, following the policy in section 4, and schedule the full scan in the nightly pipeline to catch CVEs published against code that has not changed. The underlying lesson: first you reduce the noise, then you block; the other way round, the tool disables itself.

Solution 2. By severity:

  1. pull_request_target with a checkout of the PR's code and an npm ci (critical). Anybody can open a PR from a fork with a postinstall that exfiltrates the whole environment. Fix: switch to pull_request; if privileges really are needed, split into two workflows and never run the PR's code with secrets present.
  2. Static AWS credentials exposed to that code (critical). They do not expire and remain in the attacker's hands indefinitely. Fix: remove them and use OIDC; and in a PR validation job, do not grant AWS access at all.
  3. permissions: write-all (high). The token can push to main, publish releases and modify the repository. Fix: permissions: { contents: read } in the header and add only the essentials per job.
  4. anyone/setup-tool@main (high). Referencing a third party's branch means running whatever that repository contains on every run. Fix: pin by SHA with a version comment.
  5. actions/checkout@v4 by tag (medium). Acceptable for an official action; in a strict environment, by SHA too.
  6. No timeout-minutes (low, but real): a compromised job can mine cryptocurrency for six hours before anybody notices.

Solution 3. With what has been built, the answer comes out in minutes and in three steps. (1) Are we affected? You consult the SBOM of the deployed images — attached to each image in ECR and locatable by digest — and look for the library; since the SBOM was generated against the artifact, it includes transitive dependencies and not only the declared ones. Locally, npm why <library> gives the chain that introduces it. (2) What is deployed? Each environment's /version endpoint gives the exact commit in production, and the deployments table from 03-06 gives the full history. (3) Since when? Cross-referencing the first commit whose SBOM contains the vulnerable version with the date it was deployed to prod gives the exposure window, which is the figure you will need for any customer communication.

Without SBOMs, steps 1 and 3 become manual and unreliable work: you would have to rebuild every old version — assuming its dependencies are still available — or install from each commit's lockfile and inspect the result, with hours of work and no guarantee at all about what was inside the base image, which the lockfile does not describe. That difference — minutes versus a day, certainty versus estimation — is the whole argument for the SBOM, and it is only appreciated the day you need it; which is why it is generated before you need it.

Conclusion

The pipeline has stopped being a blind spot. Reservalia now understands that its delivery system is critical infrastructure: it holds production credentials, runs third-party code and publishes the artifacts everybody trusts. On the first half of the problem — the software passing through the pipeline — it has a security job in ci.yml that runs in parallel and in two minutes executes gitleaks over the full history, CodeQL as SAST, npm audit as SCA and Trivy over the image, with the deep scan and DAST shifted to the nightly pipeline. And, more important than the tools, it has a severity policy that distinguishes new from inherited, gives each category an owner and a deadline, treats secrets as the one urgency with no nuance and documents every exclusion with a reason and an expiry: the design that stops the team learning to ignore red.

On the second half — the pipeline itself — the decisions that looked like conveniences are now conscious security decisions. OIDC is not a better-kept secret, it is the absence of a secret; the secrets that remain live in the environment that needs them and not in the repository, with a rehearsed quarterly rotation; the permissions are minimal and explicit, both in the GitHub token and in the AWS roles; third-party actions are pinned by SHA so a moved tag cannot change what runs; pull_request_target is not used; and the branch protection rules, commit signing and the GitHub and CloudTrail audit logs close the circle. There is also a written procedure for what actually happens: rotate first, investigate afterwards, clean the history last. The supply chain is closed with three answers — what an artifact contains (SBOM with Syft, attached to the image), who produced it (a cosign signature, verified before deploying) and how it was produced (provenance attestations) — deliberately placed at level 2 of SLSA. And with the warning intact: these are automated controls, not a security verdict; for that you need a professional to audit the real system and somebody who knows your organisation's legal obligations.

That makes two of the four fronts resolved, and the pipeline does more than ever: five jobs, scans, signatures and inventories. Which reopens the first front with more force, because every added control costs minutes and Diego already said it — "if CI takes longer than going for a coffee, I stop watching it". The next lesson, Scalability and Performance, measures exactly where Reservalia's pipeline time goes and trims it with the levers ordered by cost/benefit — cache, parallelisation, selective execution and runner choice — without losing sight of the risk that accompanies them all: that a badly done optimisation produces a false green, a blazingly fast pipeline that no longer checks what we think it checks.

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