The previous five lessons were labs: every step was written down, every file was handed to you and every failure was anticipated. This one is not. This one is the brief. Here you have a company context, a set of constraints, a budget and a list of requirements with their acceptance criteria, and from there you decide: which tool, which deployment strategy, which tests to write first, which scans to run on every PR and which only on main, where to put the gates, what to leave out and —hardest of all— how to justify each of those decisions to somebody who was not there when you took them.

You can do it on a project of your own (better, if you have one) or on Mini-Reservalia. What you cannot do is copy the pipeline from 07-05 as it stands: the brief's context has constraints that force you to deviate from it in at least three places, and finding them is part of the exercise.

Contents

  1. The brief
  2. The context: Citas Norte, S.L.
  3. Mandatory requirements and acceptance criteria
  4. Deliverables
  5. Self-assessment rubric
  6. A work plan in five sessions
  7. Optional challenges
  8. Typical mistakes in the final project
  9. Common Mistakes and Tips
  10. Exercises
  11. Conclusion and closing the course in practice

  1. The brief

You join a small company as delivery lead. It has a product in production and no pipeline at all. They deploy by hand, they never deploy on Fridays, and the last time something went wrong it took them two hours to go back because nobody remembered which version had been running before.

Your brief: take that product from its current state to a complete end-to-end pipeline, with quality gates, an immutable artifact, automated deployment with rollback, and metrics that prove the situation has improved.

The condition of the brief: everything you build has to be verifiable by an external reviewer who has never spoken to you, in less than an hour, looking only at the repository.

That last condition is what turns the exercise into something close to real work. A pipeline that only works when you are standing there to explain it is not a deliverable: it is a dependency.

  1. The context: Citas Norte, S.L.

Constraints are what force decisions to be justified. If everything were possible, there would be nothing to decide.

The company. Citas Norte, S.L. sells booking software for small businesses. It has:

Fact Value
Paying customers 180 businesses
Volume ~4,000 appointments a month
Technical team 3 people: a tech lead, a backend developer, and one person splitting their time between frontend and support
Product A Node.js + PostgreSQL monolith, and an SPA
Current environments Just one: production
Current deployment git pull over SSH and pm2 restart, by hand, by the tech lead
Frequency Once every two weeks, Tuesday or Wednesday morning
Tests 40 unit tests that "almost always" pass; nobody runs them before deploying
Latest incident 2 h 20 min of downtime from a deployment that broke the login

The constraints, which are non-negotiable:

  1. Tooling budget: €0/month. The company is approving no new spending until the next financial year. Everything has to fit into free plans.
  2. Nobody can be dedicated to operating the pipeline. The team is three people and all three are 100% on product. A self-hosted Jenkins is ruled out because of this, not for technical reasons.
  3. The current maintenance window is sacred to the business: on Saturday mornings, between 9:00 and 13:00, 40% of the weekend's bookings are concentrated. Downtime there costs customers.
  4. There is personal data (name, phone number and email of the end customers of those 180 businesses). Any leak is a reportable incident.
  5. The tech lead goes on holiday for three weeks in two months' time. During that period, the other two people have to be able to deploy and roll back without her.
  6. The team distrusts automation. The backend developer, specifically, has said in so many words: "every minute we spend on this is a minute we are not spending on what we get paid for". Your proposal has to pay off quickly and you have to be able to prove it with numbers.

Three consequences of reading the constraints properly (check them against your decisions when you finish):

  • Constraints 2 and 6 together rule out any solution requiring ongoing maintenance. Fewer pieces and more standard beats more powerful.
  • Constraint 5 means the rollback has to be executable by somebody who did not write it, with one button and without remembering commands. Documenting is not optional here: it is a functional requirement.
  • Constraint 3 does not mean "deployment windows". It means that time to restore matters more than frequency. If you can revert in 3 minutes, Saturday stops being frightening; if you take 2 hours, no window will save you.

The baseline you have to measure before touching anything (it is the most frequently skipped requirement in the whole project):

DORA metric Estimated current value How you would measure it today
Deployment frequency 0.5 / week Counting the production git log entries
Lead time (commit → production) ~9 days From the commit to the deployment it went out in
Change failure rate ~20% (1 in 5) Post-deployment incidents ÷ deployments
Time to restore 140 min The last incident

  1. Mandatory requirements and acceptance criteria

Every requirement comes with its verifiable criterion. The formula "an external reviewer must be able to check that…" is deliberate: if it cannot be checked by looking at the repository and its runs, it does not count.

Group A — Continuous integration (module 2)

# Requirement Acceptance criterion
A1 Reproducible build A reviewer must be able to run the same command CI runs, on their own machine, and get the same result. Lockfile committed, runtime version pinned explicitly, no dependency on prior runner state
A2 Three layers of tests There are unit tests, integration tests (against a real dependency: a database or an API) and at least one end-to-end test against the running system. The reviewer can tell which file belongs to which layer without asking
A3 Quality gate A PR with a lint failure, a broken test or coverage below the threshold cannot be merged. Verifiable by opening a test PR: the merge button is disabled
A4 Immutable artifact identified by digest The pipeline produces an artifact (a container image or equivalent) published to a registry, referenced by digest in every deployment. The reviewer can see the digest in the run summary and check that it is the same one that was deployed
A5 Branch protection main accepts no direct pushes. There is at least one required check. Verifiable by attempting a direct push: it is rejected
A6 Fast feedback The time from push to the first meaningful result is measured and documented, and is under 10 minutes

Group B — Continuous deployment (module 3)

# Requirement Acceptance criterion
B1 CI/CD separation They are separate workflows with different permissions. The CD one does not run if the CI one failed. Verifiable in the YAML and in the run history
B2 At least one environment with approval There is an environment whose deployment requires an explicit human action. The reviewer can see a run in the history that sat in the Waiting state
B3 Idempotent deployment Running the deployment twice with the same artifact changes nothing the second time. Verifiable by re-running the workflow and reading the log
B4 Post-deployment smoke test There is an automatic check that fails if the deployment was bad. It must be demonstrated: a historical run where the smoke test blocked a deployment
B5 Promotion without rebuilding The artifact that reaches the last environment is byte for byte the same one validated in the first. There is an explicit digest check in the log
B6 Timed rollback < 10 min There is a rollback procedure executable with one action, and a historical run with the time measured in the summary
B7 Rollback executable by somebody else Constraint 5 of the context. Documented in a runbook with the exact command and with no tacit knowledge

Group C — Advanced practices (module 4)

# Requirement Acceptance criterion
C1 Cache and parallelisation with measured times The PIPELINE.md documents the time before and after applying caching and parallelisation, with screenshots or links to the specific runs
C2 Logic in scripts, thin YAML The steps containing logic live in scripts/, are runnable locally and the YAML invokes them. Verifiable: the reviewer can run ./scripts/deploy.sh on their own machine
C3 At least one reusable piece A composite action, a reusable workflow or a script shared between at least two workflows, with its documentation
C4 Automated dependency management Dependabot or equivalent configured, with grouping and a policy on what is auto-merged and what is not
C5 No "false green" No `

Group D — Security (module 4 and lesson 07-05)

# Requirement Acceptance criterion
D1 Least privilege Explicit permissions: in every workflow. No write-all. The repository's default permission is read
D2 Actions pinned by SHA All third-party actions by 40-character SHA with a version comment. Verifiable with a grep
D3 All five scans Secrets, SCA, SAST, image and configuration. All five report somewhere you can look them up
D4 Severity policy There is a document saying what breaks the build and what does not, and the pipeline implements it. Exceptions have a justification and an expiry date
D5 No long-lived secrets on the critical path OIDC or ephemeral tokens are used. If there is a persistent secret, it lives in a protected environment and is justified
D6 Personal data protected Constraint 4 of the context: no test uses real data, no log prints personal data, no database dump leaves the production environment

Group E — Observability (module 3 and lesson 07-04)

# Requirement Acceptance criterion
E1 Metrics exposed The application exposes queryable metrics covering at least three of the four golden signals
E2 An SLO with an error budget Documented with its four elements (indicator, threshold, objective, window) and the budget arithmetic written out
E3 A symptom-based alert Defined with a for:, with a linked runbook, and demonstrated: there is evidence of having watched it go to firing
E4 The four DORA metrics calculated automatically A scheduled job calculates and publishes them. The reviewer can see at least two runs to compare
E5 The improvement demonstrated The PIPELINE.md compares the baseline from section 2 with the current metrics

Total: 29 requirements. Nobody expects all of them to be immaculate; what is expected is that all of them have been considered and that the absences are documented decisions, not oversights.

  1. Deliverables

4.1 The repository

Public (or private with access for the reviewer), with a real history: branches, PRs, checks, successful runs and failed runs. A repository with a single giant commit does not demonstrate a pipeline; it demonstrates a file.

Suggested structure:

project/
├── .github/
│   ├── workflows/          ci.yml, cd.yml, rollback.yml, security.yml, dora.yml
│   ├── actions/            your own composite actions
│   └── dependabot.yml
├── scripts/                all the logic: deploy, smoke, watch, dora...
├── src/  test/
├── security/               POLICY.md, exceptions.json, .trivyignore
├── observability/          compose, prometheus.yml, alerts.yml, SLO.md, RUNBOOK.md
├── Dockerfile  .dockerignore
├── PIPELINE.md             ← deliverable 4.2
├── POSTMORTEM.md           ← deliverable 4.3
└── README.md               what it is, how to run it, badges

4.2 PIPELINE.md — decisions and trade-offs

This is the deliverable that shows the most and that is done the worst. It is not documentation of what the pipeline does —that is readable in the YAML—: it is the record of why it is the way it is and what was sacrificed.

Minimum template:

# Pipeline decisions

## Context and constraints
A summary of the brief's constraints and how they shape what follows.

## Baseline (before)
| Metric | Value | How it was measured |

## Decisions

### D1 — CI/CD tool: GitHub Actions
**Alternatives considered:** GitLab CI, CircleCI, self-hosted Jenkins.
**Decision:** GitHub Actions.
**Reason:** the code is already on GitHub (identity constraint, 06-07);
the free plan is sufficient for the volume (constraint 1); zero operations
(constraint 2).
**Trade-off accepted:** vendor coupling. Mitigated by putting all the
logic in `scripts/` (C2): a migration would cost ~5 days, not 4 weeks.
**When to revisit this decision:** if the minutes consumed exceed the free
plan, or if the company changes repository provider.

### D2 — Deployment strategy: rolling, not canary
...

### D3 — What we have NOT done and why
- **No ephemeral per-PR environments.** High maintenance cost for a team
  of three (constraint 2). To be revisited when the team grows.
- **No load testing in the pipeline.** The current volume (4,000
  appointments/month) does not justify it.
...

## Measurements
| Change | Before | After | Reference run |
| Dependency cache | 3 min 40 s | 1 min 15 s | #128 vs #131 |

## After
| DORA metric | Before | Now | Source |

The "What we have NOT done and why" section is what separates professional work from an exercise. A pipeline with no explicit limits is a pipeline that will grow uncontrollably until somebody abandons it.

4.3 POSTMORTEM.md — a failure caused on purpose

Cause a real failure, let it travel as far as your pipeline stops it, and write the post-mortem. Suggested failures, in order of difficulty:

Failure Where it should be stopped What it demonstrates
A broken test In CI, in the first job The basics
A regression the tests do not cover In the staging smoke test That the layers complement each other
A backwards-incompatible DB migration In the deployment, or in the rollback What you learned in 04-06
A version that starts fine and degrades after 5 minutes In the post-deployment watch, with automatic rollback The complete loop closed

The structure of the post-mortem, blameless:

# Post-mortem: <descriptive title of the impact, not of the cause>

**Date:** · **Impact duration:** · **Severity:** · **Author:**

## Impact
What stopped working, for whom and for how long. In user terms,
not infrastructure terms: "businesses could not see the day's slots",
not "the container was unhealthy".

## Timeline
| Time | Event | Who / what detected it |
|---|---|---|
| 10:32 | PR #47 merged | — |
| 10:38 | Deployment to staging | pipeline |
| 10:39 | Smoke test fails | pipeline (automatic) |
| 10:39 | Deployment to production blocked | pipeline |

## Root cause
The five whys technique, until you reach something systemic.
If the final answer is "a developer made a mistake", you have not got to the end:
the next question is "why did the system let that mistake get there?".

## What worked
Just as important as what failed. This is where the investment in the pipeline is justified.

## What did not work

## Corrective actions
| # | Action | Owner | Date | Status |
With an owner and a date, or it is not an action: it is a wish.

## Lessons

4.4 Dashboard or metrics summary

A versioned Grafana dashboard, or —if you do not set up Prometheus— a $GITHUB_STEP_SUMMARY from a scheduled job with the four DORA metrics, the SLO status and the error budget consumption. What is being assessed is that there is a surface where somebody who is not you can see how things are going without having to ask you for anything.

  1. Self-assessment rubric

Apply it to yourself honestly. The "Inadequate" column describes what most people deliver on their first attempt.

Criterion Inadequate Solid Excellent
Reproducible build The pipeline "works" but cannot be reproduced locally Lockfile, pinned runtime, the same command locally and in CI Plus, verified periodically by a job that runs the build on a clean machine
Tests Unit tests only, or coverage with no assertions Three identifiable layers, a coverage threshold that breaks Plus, a contract test between implementations, documented edge cases and flaky detection
Quality gate The checks exist but do not block main protected with a required check, verified from the negative side An aggregator job that decouples the protection from the shape of the pipeline
Artifact Rebuilt in each environment Image published, deployed by digest Signed, with an SBOM, and the signature verified before deploying
CI/CD separation A single workflow does everything Separate workflows with different permissions Plus, the CD can be launched by hand with an arbitrary digest for retries
Approval gate There is none Environment with a required reviewer Conditional approval: automatic if the metrics are healthy, human if not
Idempotency Redeploying restarts the service needlessly The script detects it is already deployed and does nothing Plus, the script rejects mutable references and validates before touching anything
Smoke test Only checks that it returns 200 Checks health, functionality and the deployed version Plus, it checks that errors still return errors, and there is a run where it blocked
Rollback It exists as a document An executable workflow, timed, < 10 min < 3 min, automatic from metrics, and successfully executed by somebody else
Performance Not measured Caching and parallelisation applied, times documented Selective execution by changes, and the cost in minutes measured as well
Reuse Everything copied and pasted between workflows One shared, documented piece A versioned reusable workflow, with inputs, outputs and its own test
Least privilege No permissions Explicit at workflow level and widened per job Verified by provoking the failure, and with what each job needs documented
Scans None, or with ` true`
Secrets In the repository or in global variables Environment secrets, minimum scope OIDC / ephemeral tokens; no long-lived secret on the critical path
Observability Logs only Metrics exposed and a dashboard An SLO with a budget, a demonstrated alert and deployments annotated on the dashboard
DORA Not calculated Calculated by hand once A scheduled job, history and a trend compared with the baseline
PIPELINE.md Describes what the YAML does Explains why, with alternatives Includes trade-offs, explicit limits and when to revisit each decision
POSTMORTEM.md Narrates the failure Timeline, root cause, actions Blameless, with "what worked", and the actions have an owner and a date
Reproducibility by a third party Only you know how to run it A README with the steps An external reviewer verifies the whole thing in an hour without asking you anything

How to score yourself: count how many rows fall into each column.

  • Mostly in Inadequate: you have a pipeline that works but is not defensible. Prioritise group A and B4/B6.
  • Mostly in Solid: this is a professional, deliverable pipeline. It is the realistic objective of the course.
  • Several in Excellent: you are above what many teams in production have. Pick two or three to take to excellent rather than raising them all at once.

  1. A work plan in five sessions

Five sessions of 2-4 hours. The order is not arbitrary: each session leaves something working and measurable, and none of them depends on finishing the next one to deliver value. It is the same principle as the seven increments in 05-04.

Session 1 — Measure and make the project verifiable (without touching the pipeline)

  1. Measure the baseline. The four DORA metrics with whatever data you have, even if approximate. Write them into PIPELINE.md today, because in three weeks you will not be able to reconstruct them.
  2. Get the project verified with a single command: npm ci && npm run lint && npm test && npm run build. If that does not work on a clean machine, fix it before writing a line of YAML.
  3. Write the missing test for the most recent bug you had.
  4. Commit the lockfile if it is not there.

Session deliverable: one command that verifies the project, and the baseline written down.

Session 2 — CI and the first gate

  1. ci.yml incrementally, as in 07-01: first get it to run, then get it to install and test, then jobs in parallel, then caching. Measure the times at each step and note them down.
  2. The three layers of tests (07-02). Start with the one you are most afraid of breaking.
  3. Coverage with a threshold 2-3 points below the current value.
  4. Protect main with an aggregator job as the only required check.
  5. Break something on purpose and check that it blocks.

Deliverable: A1-A6 met, with the before/after times for the cache.

Session 3 — Artifact and deployment

  1. Multi-stage Dockerfile, non-root, HEALTHCHECK (07-03).
  2. Publication to the registry by digest, with the digest visible in the summary.
  3. scripts/deploy.sh, idempotent and runnable locally. Write it before the YAML: if you start with the YAML, you will end up with logic inside the YAML.
  4. scripts/smoke.sh checking health, functionality and version.
  5. cd.yml with two environments and approval on the second.
  6. rollback.yml, timed. Run it and note the time.
  7. Cause a bad deployment and check that the smoke test stops it.

Deliverable: B1-B7 met, with the timed rollback run and the blocked deployment run.

Session 4 — Security

  1. An audit of what you have built so far, using the list from 07-05.
  2. Least-privilege permissions, provoking a failure to understand it.
  3. Actions by SHA + Dependabot.
  4. The five scans, one at a time, checking that each one detects something real before moving on to the next.
  5. A written severity policy and the exception expiry job.
  6. SBOM, signature and verification before deploying.

Deliverable: D1-D6, with evidence that each scan has detected something.

Session 5 — Observability, closing and documentation

  1. /metrics with the golden signals (07-04).
  2. An SLO with the budget arithmetic written out.
  3. A symptom-based alert, fired on purpose and with a screenshot.
  4. A scheduled DORA job. Run it twice a few days apart so you have a trend.
  5. Cause the post-mortem failure and write it up.
  6. A complete PIPELINE.md, including the section on what you have not done.
  7. Take the rubric.

Deliverable: E1-E5, PIPELINE.md, POSTMORTEM.md and the self-assessment.

A word on pacing. If you are short of time, sacrifice group E before group B. A pipeline that deploys and reverts but is not measured is incomplete; one that is measured but cannot revert is dangerous. And if you have to pick a single requirement out of the 29, make it B6: a timed rollback. It is the one that turns fear into a number.

  1. Optional challenges

For once the basics are closed off. Each one stands on its own.

Challenge What it adds Difficulty Reference
A real canary with an automatic decision Deployment by weight + measurement + automatic promotion or withdrawal High 03-04, 07-03 ex. 2
Ephemeral per-PR environments One environment per pull request, destroyed when it closes High 02-07, 06-02
A matrix of two tools The same pipeline in GitHub Actions and in GitLab CI, comparing the real effort Medium The whole of module 6
IaC for the target environment Terraform that creates the host, the registry and the network, with plan on the PR and apply after approval High 03-03
DB migrations in the pipeline Expand and contract, a versioned migration, and a rollback that loses no data Very high 04-06
An external admission policy A script that validates signature, SBOM, scans and provenance, consulted by the CD Medium 07-05 challenge
Shard distribution by timing Real balancing of the suite using history Medium 07-02 challenge

The most instructive of all, if you want to close the course with something that changes your perspective, is the matrix of two tools: reimplementing the pipeline in GitLab CI forces you to separate what is your process from what is GitHub syntax, and that separation is exactly what 06-07 was arguing for. It takes less time than it looks —the real work is already in the scripts— and the result is an equivalence table you wrote yourself.

  1. Typical mistakes in the final project

These three are, by a distance, the most repeated. All three have the same shape: doing the visible part before the part that holds it up.

Mistake 1: starting with the YAML instead of the scripts

How it shows up. You open ci.yml and start typing. Three days later there are 200 lines of YAML with twenty-line run: | blocks each, and the only way to test a change is to push and wait four minutes.

Why it matters. The debugging cycle goes from seconds to minutes, and multiplied by every attempt. A problem that locally takes five ten-second iterations turns into five four-minute iterations, with commits saying "fix CI", "fix CI 2", "now for real". On top of that, that logic is not testable, not reviewable, and cannot be run in an emergency if CI is down.

The right order:

1. The command works in your terminal
2. The command lives in a script with arguments and defaults
3. The script is in the repository and has its documentation
4. The YAML CALLS the script

The acid test: can you deploy without GitHub Actions? If the answer is no, the logic is in the wrong place.

Mistake 2: automating before you have a test worth having

How it shows up. A beautiful pipeline that deploys in ninety seconds... broken code. The tests exist, they always pass, and they cover none of what actually breaks.

Why it matters. Automating deployment multiplies the speed at which changes reach production. If the verification is weak, what you have multiplied is the speed at which bugs reach production. A fast pipeline on top of bad tests is objectively worse than the manual deployment you started with, because the manual one at least involved a person looking.

The right order: a test that catches the last real bug you had, before automating the deployment. If you cannot write it, do not automate yet: automate only as far as staging and leave production manual until the safety net exists.

The warning sign: if your suite has never gone red because of a real bug —only because of syntax errors— it is not testing anything.

Mistake 3: not measuring the baseline and ending up unable to prove the improvement

How it shows up. Three weeks of work, an excellent pipeline, and then in the meeting with management: "so what has this improved?""Well... everything is much better".

Why it matters. It is the mistake that stops the next investment being approved. Remember constraint 6 of the context: the backend developer already thinks this is time stolen from the product. Without numbers, he is right, because his scepticism is based on something concrete (the hours invested) and your defence is not.

And there is a technical aggravating factor: the baseline cannot be reconstructed after the fact. Once the pipeline is in place, the "before" no longer exists. It is information destroyed by doing the work.

The right order: measure in session 1, before touching anything, even with rough estimates. Four numbers and the date. Ten minutes of work that pay for the next three weeks.

How you present it afterwards:

Metric Before (12/03) Now (28/04) Change
Deployment frequency 0.5 / week 6 / week ×12
Lead time 9 days 4 h −98%
Change failure rate 20% 6% −70%
Time to restore 140 min 3 min −98%
Tech lead's time spent deploying 45 min/deployment 0 −6 h/month

That last row is the one that ends the discussion, and that is why it is worth having: it translates the work into person-hours, which is the unit whoever approves budgets thinks in. It is exactly the TCO argument from 06-07, applied to your own project.

  1. Common Mistakes and Tips

Symptom: the project gets stuck in session 3 and never reaches production. Cause: trying to set up the "real" target environment (a VPS, Kubernetes, AWS) before having the pipeline. Fix: use a simulated target like the one from 07-03 and complete the whole circuit. A pipeline that deploys to a local container but covers the seven pieces of B1-B7 is worth infinitely more than half a pipeline pointing at real infrastructure. Change the target at the end; it is the easiest part to change.

Symptom: 29 requirements look unmanageable and you never start. Cause: trying to do them in parallel. Fix: the five-session plan is ordered by dependency. Finish A completely before touching B. One finished group is worth more than five half-done ones, because incomplete groups cannot be verified.

Symptom: the PIPELINE.md ends up being a description of the YAML. Cause: documenting while writing the code, when there is still no perspective. Fix: write each decision down at the moment you discard an alternative, in one line: "discarded X because Y". By the end, those lines are the document. If you never discarded anything, you did not make decisions: you copied.

Symptom: the rubric comes out mostly in "Solid" and you do not know what to improve. Tip: do not push everything to "Excellent". Pick two or three deliberately: the ones that most reduce risk in your specific context. With Citas Norte's constraints, the three obvious candidates are the rollback (constraint 5), the signature verification (constraint 4, personal data) and DORA with a trend (constraint 6, somebody has to be convinced).

Tip — do not do it all yourself. If you have somebody available, ask them to verify your repository following the acceptance criteria, without you explaining anything. The points where they have to ask you something are exactly the ones you still need to document. It is the cheapest and most revealing test in the project.

Tip — the pipeline is never finished. When you close all 29 requirements, the pipeline will be good for today's context. With twice the team and ten times the traffic, three of your decisions will be wrong. That is why every decision in the PIPELINE.md carries a "when to revisit this decision": it is not bureaucracy, it is acknowledging that a pipeline is a living organism and that the alternative to revisiting it is rewriting it entirely two years from now.

  1. Exercises

These three are not lab variations: they are extensions of the final project itself, designed to be done after delivering it.

Exercise 1: the peer review

Swap repositories with somebody else who has done the project (or audit a real public repository that uses GitHub Actions). Apply the 29 acceptance criteria without talking to anyone and write an audit report with the findings prioritised.

Exercise 2: the holiday test

Constraint 5 of the context: the tech lead is away for three weeks. Prove the system survives.

Exercise 3: the same pipeline in half the time

Halve your pipeline's time without removing a single check. Document every change with its measurement.

Solutions

Solution 1. Audit report template:

# Pipeline audit of <repository>
**Auditor:** · **Date:** · **Time spent:** · **Commit audited:**

## Executive summary
Three sentences: overall state, the most serious risk, the improvement with
the best value/effort ratio.

## Criteria verification
| # | Requirement | Meets it | Evidence | Notes |
|---|---|---|---|---|
| A1 | Reproducible build | ✅ | I ran `npm ci && npm test` from clean: OK | — |
| A4 | Artifact by digest | ⚠️ | Run #88: publishes by digest but the CD uses `:main` | **Promotion does not guarantee the same artifact** |
| B4 | Smoke test | ❌ | I found no run where it blocked anything | It may never have been tested |

## Prioritised findings
### F1 (High) — The CD deploys by tag, not by digest
**Evidence:** `cd.yml:47` uses `ghcr.io/...:main`.
**Risk:** between validation in staging and deployment to production, another
merge can move the tag. Production would run unvalidated code.
**Fix:** resolve the digest once in a `prepare` job and propagate it through
`outputs`. Cost: ~30 min.

## What is done well
(Mandatory. An audit that only lists problems does not get read to the end and
does not get acted on.)

## Questions I had to ask the author
Every one of these is missing documentation.

What this exercise teaches: auditing somebody else's work is the fastest way to see the holes in your own. Almost everybody finds two or three things in the other repository that their own is missing too and that they had not spotted in weeks.

Solution 2. The protocol for the test:

# Continuity test ("the holiday")

## The rule
During the exercise, whoever wrote the pipeline **does not speak, does not touch
the keyboard and does not answer questions**. They only observe and take notes.

## Scenario 1 — Routine deployment (target: < 15 min)
The other person must, using only the repository:
1. Work out how a deployment is done.
2. Launch it.
3. Approve the production gate.
4. Check that the new version is serving traffic.

## Scenario 2 — Rollback under pressure (target: < 10 min)
A bad version is deployed (it breaks `/health`). The other person must:
1. Notice that something is wrong (did the alert arrive? to whom?).
2. Find which version to go back to.
3. Run the rollback.
4. Confirm the recovery.

## Scenario 3 — Pipeline failure (target: deploy without CI)
GitHub Actions is down (simulate it by disabling the workflow) and an urgent
patch has to be deployed. Is there a documented manual path?

## Record
| Scenario | Time | Did they manage it? | Where they got stuck | Documentation that was missing |

## Corrective actions
Every sticking point is a failure of the SYSTEM, not of the person. It turns into:
- A line in the runbook, or
- A better default, or
- A clearer error message.

The three failures that show up almost every time in this exercise, in case you want to get ahead of them:

  1. Finding the previous digest. Nobody knows where to look. Fix: print it in the summary of every deployment and accept an empty input in the rollback (07-03 exercise 1).
  2. The alert reached nobody. It was configured in Prometheus but had no recipient. Fix: Alertmanager with a real channel, and proof that it arrives.
  3. The manual path does not exist. All the knowledge is inside the YAML. Fix: scripts/deploy.sh runnable locally (requirement C2), documented in the runbook with the literal command.

If your project passes scenario 3, you have met the most demanding criterion in the whole course: the pipeline is a convenience, not a dependency.

Solution 3. The strategy, in order of return:

# Reducing the pipeline time

## Initial measurement (run #142)
| Job | Duration | Critical path? |
|---|---|---|
| quality | 1m 10s | No |
| test (×4) | 3m 40s | **Yes** |
| coverage | 2m 50s | No |
| build | 2m 20s | Yes |
| publish | 4m 10s | **Yes** |
| **Total (wall clock)** | **10m 30s** | |

Rule to apply first: **only the critical path counts**. Optimising a job that runs
in parallel with a slower one saves not a single second of wall-clock time.

## Changes applied

### C1 — Dependency cache (−1m 50s)
`cache: 'npm'` in the 5 jobs. From the second run onwards.
Before: `npm ci` 22s/job · After: 4s/job.

### C2 — Docker layer cache (−2m 30s)
`cache-from: type=gha` / `cache-to: type=gha,mode=max`.
The biggest single saving: the native module stopped being recompiled.

### C3 — Reordering the Dockerfile (−40s)
`COPY package*.json` and `npm ci` BEFORE `COPY . .`.
Without this, any code change invalidates the dependency layer
and the C2 cache is useless. **C3 is what makes C2 work.**

### C4 — Merging `coverage` into the test job (−0s wall clock, −2m machine)
It was not on the critical path: it saves no wall-clock time, but it does save cost.
It is documented because cost is a metric too.

### C5 — Selective execution (−variable)
The heavy jobs only if the relevant files change, with an aggregator job
that always reports (07-01 exercise 2). On documentation-only PRs:
10m 30s → 45s.

### C6 — Discarded: larger runners
Would cut ~30% but costs money. Constraint 1 of the brief.
**The rejection is documented so nobody has to evaluate it again.**

## Result (run #171)
| Job | Before | After |
|---|---|---|
| test (×4) | 3m 40s | 1m 30s |
| publish | 4m 10s | 1m 20s |
| **Total (wall clock)** | **10m 30s** | **4m 15s** (−60%) |
| Machine minutes | 14m 10s | 7m 30s (−47%) |

## Checks NOT removed
None. The same 5 scans, the same coverage, the same matrix.
`git diff` of the workflows: only caching, ordering and conditions.

The lesson of this exercise is C3: the Docker cache does not work if the Dockerfile is badly ordered, and plenty of people add C2, see no improvement and conclude that "caching is useless". Layer ordering is 80% of the result, and it is free.

  1. Conclusion and closing the course in practice

If you have got this far with the project delivered, you have something in your repository that many teams in production do not have: a pipeline that integrates, verifies in three layers, builds an immutable artifact identified by its content, scans it in five different ways, signs it, deploys it behind an explicit gate, checks that the deployment worked, promotes the very same artifact without rebuilding it, reverts in minutes —on its own, if the metrics get worse—, and measures itself so it can prove that all of that is worth something.

But the deliverable that will last longest is not the YAML. It is the two documents.

The PIPELINE.md, because it contains the one thing you cannot deduce by reading the code: why it is the way it is and what was sacrificed in return. A year from now, when somebody —perhaps you— wonders why there is no canary or why the actions are pinned to those strange SHAs, the answer will be written down, with its date and with the condition that would force a review. A pipeline without that document gets rewritten from scratch every two years because nobody dares touch what they do not understand.

And the POSTMORTEM.md, because it documents the only proof that really matters: that the system failed and held up. Anybody can show you a pipeline in green. Showing one that went red for the right reason, in the right place, and recovered in three minutes, is showing that it works.

There is one last thing this module has made you do without saying so explicitly, and it is worth naming now. In every lab you have broken something on purpose: a test, a deployment, a secret, a SQL query, a slow endpoint. That was not pedagogical decoration. It is the professional practice that separates whoever has a pipeline from whoever trusts their pipeline: verifying from the negative side. Anybody can check that a control lets the good stuff through; checking that it stops the bad stuff requires manufacturing the bad stuff, and almost nobody does it. If you take a single habit away from this course, let it be this one: every time you add a gate, cause the failure it is supposed to stop, and do not take the gate as good until you have watched it happen.

That closes the practical part and, with it, the taught material of the course. You have covered the principles (modules 1 to 4), four real contexts (module 5), the machinery (module 6) and end-to-end construction with your own hands (module 7). What remains is no longer material: it is where to go next.

Module 8 is the map of that continuation. The recommended readingContinuous Delivery, Accelerate, The DevOps Handbook, the SRE Workbook— that provides the grounding and the data behind almost everything you have applied here. The communities and forums where these problems are discussed once they outgrow what fits in a course. The additional tools and plugins that were left out for space and that solve specific problems you will now be able to recognise: secret management with Vault, progressive delivery with Flagger or Argo Rollouts, internal developer platforms, policy as code. And the learning path and certifications, with a sensible order for going deeper depending on where you want to move —platform, SRE, supply chain security— and which certifications are genuinely worth it in each case.

Start with 08-01: Recommended Reading. And when you do, read it with the pipeline in front of you: you now have a real system to try every idea on, which is exactly what was missing the first time you opened this course.

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