At the close of module 3 one question was left unanswered: how many packages really get into reservalia/api:a3f9c21, who maintains them and which exact version was used. It is the second of the four outstanding fronts, and the quietest of them all: nobody opens a ticket saying "the dependencies are wrong", but on any given Monday CI goes red without anybody having touched the code, or a minor update changes the behaviour of a function and bookings for businesses with split opening hours start being calculated wrongly. This lesson turns that blind spot into something managed: what exactly a lockfile guarantees and what it does not, how version ranges are chosen, how updates are automated without drowning the team in pull requests, how transitive dependencies are audited, how they are cached in the pipeline without breaking reproducibility, and when an organisation ends up needing its own registry. What we will not cover here are vulnerabilities or supply chain attacks — malicious packages, npm audit as a quality gate, artifact signing: all of that is lesson 04-03, and it builds on what we construct today.
Contents
- Reservalia's real inventory
- What exactly a lockfile guarantees
npm civersusnpm install, and what happens if somebody edits the lockfile by hand- Lockfiles in other ecosystems
- Version ranges and the trade-off between patches and reproducibility
- Transitive dependencies: seeing them, understanding them and forcing them
- Automated updating: Reservalia's
dependabot.yml - Update strategy: what merges itself and what gets reviewed
- Dependency caching in the pipeline and its invalidation
- Private registries and mirrors
- Choosing a library and retiring abandoned ones
- Common Mistakes and Tips
- Exercises
- Conclusion
- Reservalia's real inventory
Marta asks for the figure and Diego produces it in thirty seconds:
npm ls --all --workspace apps/api | wc -l # 1 · the full tree
npm ls --omit=dev --all --parseable | sort -u | wc -l # 2 · only what reaches productionnpm ls --allunfolds the entire tree, including the dependencies of the dependencies. Without--allyou would only see the first level, which is the part you already knew about.--omit=devdiscards development tooling, which does not travel inside the image.--parseableprints paths rather than a tree, so they can be sorted and counted without duplicates.
The result surprises everybody the first time:
apps/api |
apps/web |
Monorepo total | |
|---|---|---|---|
| Direct production dependencies | 14 | 9 | 23 |
| Development dependencies | 21 | 18 | 39 |
| Total packages in the tree | 612 | 891 | 1,147 |
| Packages that end up inside the image | 418 | — | 418 |
| Distinct maintainers involved | ~330 | — | — |
Twenty-three conscious decisions turn into more than a thousand packages written by people the team does not know, published at hours it does not control and executed with the same permissions as Reservalia's own code. The risk is not hypothetical: in module 3 we saw that the change failure rate stalled at 6.5%, and one of the identified causes was exactly this — a minor update of a date library that changed the behaviour of parseISO with time zones and broke calculateSlots for businesses with split opening hours, with no test catching it because none covered that case. The rule that orders the rest of the lesson: a dependency is not free code, it is code you have adopted. If it fails, the customer calls Reservalia, not the maintainer.
- What exactly a lockfile guarantees
A package.json declares intentions ("express": "^4.19.2" means "any 4.x from 4.19.2 onwards"). A package-lock.json records facts: the exact version of every package in the tree, where it was downloaded from and its cryptographic fingerprint.
{
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"engines": { "node": ">= 0.10.0" }
}
}Three fields and three different guarantees. version pins which version is installed, so two installations six months apart get the same tree. resolved pins where it comes from, which matters when several registries are configured. And integrity is a hash of the package's contents: if what is downloaded does not match, npm aborts the installation. It is the difference between "I asked for version 4.19.2" and "I received exactly these bytes". Now the part almost nobody explains: what a lockfile does NOT guarantee.
| It does guarantee | It does not guarantee |
|---|---|
| The same version of every package | That the package will still be available (it can be removed from the registry) |
| The same content, hash-verified | That install scripts do the same thing (they compile according to the machine) |
| The same resolution tree | The same version of Node, of the system or of the OS libraries |
| Reproducibility of the dependencies | Reproducibility of the complete build |
That is why the lockfile is a necessary but not sufficient condition, and why Reservalia accompanies it with an .nvmrc that pins the Node version and a pinned base image (node:20.11.0-bookworm-slim). The three pieces together — lockfile, .nvmrc, base image — are what make a3f9c21 mean something concrete.
npm ci versus npm install, and what happens if somebody edits the lockfile by hand
npm ci versus npm install, and what happens if somebody edits the lockfile by handnpm install |
npm ci |
|
|---|---|---|
| Reads the lockfile | Yes, but it can modify it | Yes, and it never modifies it |
If package.json and the lockfile disagree |
It resolves and rewrites the lockfile | It fails with an error |
Pre-existing node_modules/ |
It reuses and patches it | It deletes it and reinstalls |
| Speed in CI | Lower and variable | Higher and constant |
| Where to use it | On your laptop, when adding a package | In the pipeline, always |
The decisive row is the second one. npm ci requires the lockfile to be consistent with the package.json, and that behaviour is exactly what you want in CI: if Diego adds "zod": "^3.23.0" to the package.json and forgets to commit the updated lockfile, the quality job fails in twenty seconds with a clear message, instead of quietly installing a version nobody has recorded. The check is free and eliminates a whole category of "it works on my machine". Editing the lockfile by hand is a temptation that shows up when resolving a merge conflict, and it deserves an explicit warning. A package-lock.json is not a list, it is a resolved graph: changing a version by hand does not recompute that dependency's own dependencies, so you can leave the tree in a state npm would never have produced — a library declaring it needs >=5.0.0 coexisting with 4.8. The result is run-time failures that are impossible to reproduce. The right way to resolve a lockfile conflict is to regenerate it:
git checkout --theirs package-lock.json # 1 · keep either version, it does not matter
npm install # 2 · let npm recompute the graph
git add package-lock.json- The specific content is irrelevant, because it is going to be rewritten entirely.
npm install— here yes, notci— recomputes the tree from the already mergedpackage.jsonfiles. Afterwards it is worth looking at the resulting diff: if fifty unexpected changes appear, somebody had pinned something by hand.
- Lockfiles in other ecosystems
The concept is universal even though the names change. If you work outside Node, this table translates everything above:
| Ecosystem | Intentions file | Lockfile | Reproducible command in CI |
|---|---|---|---|
| npm | package.json |
package-lock.json |
npm ci |
| Python + Poetry | pyproject.toml |
poetry.lock |
poetry install --sync |
| Python + pip | requirements.in |
requirements.txt with hashes |
pip install --require-hashes -r requirements.txt |
| Maven | pom.xml |
None: exact versions in the POM | mvn -B verify with fixed versions |
| Gradle | build.gradle |
gradle.lockfile (must be enabled) |
gradle build after --write-locks |
| Go | go.mod |
go.sum (hashes) |
go build -mod=readonly |
| Rust | Cargo.toml |
Cargo.lock |
cargo build --locked |
| PHP | composer.json |
composer.lock |
composer install --no-dev |
Two useful observations. Maven is the exception: it has no lockfile of its own, and reproducibility depends on you not using LATEST or RELEASE versions and on the dependencyManagement blocks being complete; that is why serious Java teams add a plugin that pins the tree. And the pattern in the right-hand column is always the same: there is an "install exactly what the lockfile says and fail if it does not match" mode. That is the command that goes in the pipeline; anything else is npm install in disguise.
- Version ranges and the trade-off between patches and reproducibility
Semantic versioning (semver) gives MAJOR.MINOR.PATCH a contractual meaning: major = incompatible change, minor = new compatible functionality, patch = compatible fix.
| Range | Meaning | Accepts | When to use it |
|---|---|---|---|
^4.19.2 |
Compatible with 4.19.2 | 4.19.3, 4.20.0 · not 5.0.0 | The default in an application |
~4.19.2 |
Patches only | 4.19.3 · not 4.20.0 | Sensitive or unstable dependencies |
4.19.2 |
Exact | Only that one | Tools whose behaviour must be identical |
* or latest |
Anything | Everything | Never |
The point that confuses many people: with a lockfile, the range hardly matters for reproducibility. The lockfile pins the installed version, so ^4.19.2 and 4.19.2 produce exactly the same tree today. What the range decides is what is accepted when the lockfile is regenerated: on running npm update, on adding a package or when Dependabot proposes a bump. And there is where the real trade-off lies. A wide range (^) lets fixes arrive without friction, but it bets on the maintainer respecting semver, which is not always the case — the parseISO incident in section 1 was precisely a minor version with a behaviour change. A narrow range removes surprises but turns every fix into manual work, and a project with 23 direct dependencies pinned to exact versions is out of date within six months.
Reservalia's stance, advisable for most teams: ^ by default in the package.json, the lockfile always committed to git, and updates arriving through a reviewed pull request with green CI, never through automatic resolution at install time. That way the range describes what is acceptable and the lockfile describes what is used, and moving from one to the other is always a visible commit.
- Transitive dependencies: seeing them, understanding them and forcing them
A transitive dependency is one you did not ask for: it comes in because one of your dependencies needs it. Of Reservalia's 1,147 packages, 1,124 are transitive. They do not appear in the package.json, nobody chose them and they are the overwhelming majority of the code that runs.
npm ls date-fns # 1 · who brings this package in and in which version
npm why date-fns # 2 · the chain of reasons, in npm 9 and later
npm outdated # 3 · what is behind and by how muchnpm ls <package>prints the path from your direct dependencies down to it. It is the tool that answers "and where did this come from?", which is the first question of almost any investigation.npm whygives the same thing in explanation form. If two different versions of the same package appear, npm has installed both — one at the root and one nested — and that causes baffling errors when the package holds global state.npm outdatedcompares what is installed with what is published and separates "what your range allows" from "the latest that exists". It is the best thermometer of dependency debt in ten seconds.
When a transitive version has to be forced — typically because 2.3.1 has a bug and you need 2.3.2, but whoever brings it in has not updated yet — npm offers overrides:
The first form replaces that version across the whole tree, wherever it comes from; the second limits the replacement to the subtree of one specific package, which is safer when you do not know whether the other consumers tolerate the change. An override is declared technical debt and must be treated as such: you are asserting that a version different from the one the maintainer tested works the same, and nobody has verified that beyond your tests. Reservalia imposes two rules on itself: every entry carries a comment with the reason and a link to the original issue, and the list is reviewed once a quarter to remove what is no longer needed. The equivalent in other ecosystems is resolutions (Yarn), dependencyManagement (Maven) or replace (Go).
- Automated updating: Reservalia's
dependabot.yml
dependabot.ymlWith 1,147 packages, keeping up to date by hand is impossible; not doing it is accumulating a giant migration for two years' time. The solution is a bot that opens update pull requests: Dependabot (built into GitHub) or Renovate (more configurable and available on any platform). Reservalia chooses Dependabot because it is already in the house:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: "/" # 1 · one lockfile: the root is enough
schedule:
interval: weekly
day: tuesday # 2 · Tuesday, not Monday or Friday
time: "06:00"
timezone: Europe/Madrid
open-pull-requests-limit: 5 # 3 · a ceiling on PRs open at once
groups: # 4 · group them so as not to drown the team
dev-minor:
dependency-type: development
update-types: [minor, patch]
prod-patches:
dependency-type: production
update-types: [patch]
ignore:
- dependency-name: "typescript" # 5 · majors by hand
update-types: [version-update:semver-major]
labels: [dependencies]
commit-message: { prefix: "chore(deps)" }
- package-ecosystem: github-actions # 6 · actions are dependencies too
directory: "/"
schedule: { interval: monthly }
groups:
actions: { patterns: ["*"] }directory: "/"points at where thepackage-lock.jsonlives. Since Reservalia uses npm workspaces with a single lockfile at the root, one entry coversapps/api,apps/webandpackages/shared-types. With separate lockfiles you would need one entry per folder.- Tuesday first thing. On Monday the team clears the weekend's inbox and on Friday it leaves PRs open until Monday. It is a minor detail that changes the real review rate a great deal.
open-pull-requests-limitis the protection against the avalanche effect. Without it, the first run on a neglected repository opens forty pull requests, the team is overwhelmed and ignores them all: the bot goes from helping to being background noise.groupsis the most valuable option in the file: instead of one PR per package, it bundles several into one. Reservalia puts all the minors and patches of development tooling into one weekly PR, and the production patches into another. Twenty PRs become two or three.ignorefor TypeScript majors: a major bump oftsccan throw up hundreds of new errors, and that is planned work, not a Tuesday PR.- GitHub actions are dependencies like any other, and they are usually forgotten.
uses: actions/checkout@v4is third-party code executed with access to the repository; how they are pinned safely is the business of 04-03.
One detail decides whether all of this works: an automatic update is only safe if the CI from module 2 is trustworthy. The bot does not read the changelog and does not understand your business; the only thing separating "an update applied without drama" from "a regression in production" is that the tests cover what matters. If the suite is weak, automating dependencies does not reduce the risk: it accelerates it. That is why this front comes after module 2 and not before.
- Update strategy: what merges itself and what gets reviewed
A bot that opens PRs with no policy about what to do with them merely moves the work elsewhere. This is the table Reservalia writes in the README.md:
| Type of update | Action | Who | Rationale |
|---|---|---|---|
| Patch to a development dependency | Auto-merge with green CI | Nobody | Risk close to nil: it does not reach production |
| Minor to a development dependency | Auto-merge with green CI | Nobody | Same; they come bundled in the weekly PR |
| Patch to a production dependency | Quick review and merge | Weekly rota | It is usually a fix; CI covers the rest |
| Minor to production | A real review: read the changelog | Weekly rota | This is where the parseISO incident lives |
| Major of any kind | A planned ticket | Marta assigns | Incompatible changes: it is work |
| Security update | Top priority | Nuria | See 04-03 |
Three decisions deserve an explanation. Auto-merge is not sloppiness: it rests on the pipeline blocking the merge if anything fails — the quality gate from 04-01 — and on those dependencies not travelling inside the image. It is enabled with one line:
Production minors get a human review precisely because of the incident in section 1. Semver is a promise, not a mechanism: a behaviour change with no API change is formally "minor" and functionally a bomb. Reading a package's changelog costs two minutes and is the only real defence. There is a weekly rota with a name on it. Without an assigned person, updates are everybody's responsibility, which is the same as nobody's. Reservalia rotates the duty every week with a time box — half an hour on Tuesdays; what does not fit waits for the next one, but it never piles up in silence.
- Dependency caching in the pipeline and its invalidation
Downloading 1,147 packages in every job is the slowest and least interesting part of CI. The cache removes it, but it introduces an obvious risk: if it serves the wrong cache, the pipeline verifies something that is not your code. The solution is for the cache key to be a hash of the lockfile.
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm # 1 · shortcut: it already uses the lockfile hash
- run: npm ci
# The explicit equivalent, to understand what the shortcut does:
- uses: actions/cache@v4
with:
path: ~/.npm # 2
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }} # 3
restore-keys: npm-${{ runner.os }}- # 4cache: npminsetup-nodedoes exactly what is written below it. It is what Reservalia has used since 02-02; here we see why it works.~/.npmis cached, notnode_modules/. The first is npm's download cache, platform-independent; the second contains binaries compiled for a specific system and internal links, and restoring it on another machine produces failures that are hard to diagnose. Restoring~/.npmand runningnpm ciis fast and correct.- The key contains the lockfile hash. If the lockfile changes by a single byte, the key changes and the previous cache is not used: invalidation is automatic and does not depend on anybody remembering.
restore-keysis the safety net: if there is no exact match, the most recent cache starting with the same prefix is restored and npm only downloads what is missing. That way a dependency update does not force everything to be fetched from scratch.
The general rule, valid for any pipeline cache: the key must derive from the content of the inputs. If the key is fixed (key: npm-cache), the cache is never invalidated and you end up running tests against old dependencies — a false green, the cross-cutting risk developed in 04-04. And a rule of hygiene: the cache is an accelerator, never a source of truth; the pipeline must work correctly if it is emptied entirely, just more slowly.
- Private registries and mirrors
Reservalia installs directly from registry.npmjs.org, and with 340 paying businesses that is starting to be uncomfortable for three concrete reasons: if the public registry has an outage, you cannot deploy — and responding to an incident may require deploying; there is nowhere to publish @reservalia/shared-types the day it has to be shared with the mobile application in module 5; and there is no single point at which to apply a policy of the sort "this library is not used at this company".
| Option | What it gives you | Cost | When it makes sense |
|---|---|---|---|
| Public registry directly | Total simplicity | Zero | Small teams with no packages of their own |
| GitHub Packages | Private packages with the repository's permissions | Very low if you already use GitHub | Reservalia's natural next step |
| Artifactory / Nexus | Public mirror + private registry + policies | High: it is infrastructure to maintain | Several dozen developers or audit requirements |
| Read-only mirror | Isolates you from outages and withdrawn packages | Medium | When deployment cannot depend on a third party |
The configuration is one file, and it is worth understanding what each line does:
# .npmrc
@reservalia:registry=https://npm.pkg.github.com # 1 · only our own scope
//npm.pkg.github.com/:_authToken=${NPM_TOKEN} # 2 · from a variable, never literal
registry=https://registry.npmjs.org # 3 · everything else, from the public one- Only
@reservalia/*packages are looked up in the private registry. Redirecting everything to a private registry with no mirror configured breaks the installation of the other 1,100 packages. - The token is read from an environment variable. An
.npmrcwith a literal token committed to git is a credential leak, and it is surprisingly common. - The order matters: the general line goes last and acts as the default.
And a warning about the well-known "package that disappears": a maintainer can withdraw a version from the public registry and break builds that had been working for years. The lockfile's integrity does not help — it verifies the content, it does not guarantee availability. Only a mirror with a local copy protects against that, and it is the main reason a mid-sized organisation eventually sets one up.
- Choosing a library and retiring abandoned ones
The best dependency management is not having the dependency. Before adding one, Reservalia asks itself five questions in this order:
- Can I solve it with the standard library? Node 20 ships
fetch, a test runner,crypto.randomUUID()and a good deal more. Many historical dependencies are surplus today. - How much code am I adopting? A twenty-line utility copied in with its own tests is preferable to a package that drags in another thirty.
- Is it alive? Recent commits, issues that get answered, more than one maintainer, a stable major version.
- How much would it cost to get out? A library encapsulated behind a module of your own is replaced in a day; one whose types appear in two hundred files is a marriage.
- What licence does it have? It usually gets looked at too late, and it is a legal problem, not a technical one.
At the other extreme are abandoned dependencies, with clear symptoms: no releases in two years, unanswered issues, a deprecation warning on install or incompatibility with new versions of the language. The right reaction is not urgency — a stable, unchanging library may simply be finished — but record-keeping: note it on a risk list, encapsulate it behind an interface of your own so it can be swapped without touching two hundred files, and plan the replacement for when it blocks something. What you must not do is discover it the day a security flaw forces an update and it turns out there is no version to update to.
Common Mistakes and Tips
Mistake 1: not committing the lockfile to git. Without it, every installation resolves on its own and "it works on my machine" becomes a valid argument again. Mistake 2: using npm install in the pipeline, which can rewrite the lockfile inside the runner and make CI verify a different tree from the one you reviewed.
Mistake 3: editing the lockfile by hand to resolve a conflict, leaving a graph npm would never have produced; regenerate it. Mistake 4: pinning everything to exact versions "to be safe", which freezes the project and turns every fix into manual work.
Mistake 5: enabling Dependabot with no groups and no PR limit. Forty pull requests on the first Tuesday, and the team learns to ignore the dependencies label for ever. Mistake 6: automating updates with a weak test suite: you do not reduce the risk, you accelerate it. Mistake 7: caching node_modules/ instead of ~/.npm, or using a fixed cache key that is never invalidated; both produce false greens. Tip 1: look at the tree once a quarter. npm ls --all and npm outdated in ten minutes give a very clear idea of where the next scare will come from. Tip 2: encapsulate the important dependencies behind a module of your own, so replacing them is a day's work. Tip 3: treat overrides as debt, with a comment, a reason and a quarterly review.
Exercises
Exercise 1
A team's CI goes red on a Monday morning without anybody having touched the code: the last commit is from Friday and it was green then. In the pipeline they use npm install. Explain in detail what has happened, why the package-lock.json did not prevent it and which two changes prevent it for good.
Exercise 2
Design the dependency update policy for a team of 4 people with a suite covering 45% of the code and no integration tests. State what you would change relative to the table in section 8 and why, and what you would do first.
Exercise 3
Reservalia needs version 7.6.2 of semver because 7.5.4 has a bug, but it is brought in transitively by some-linter and its maintainer has not published the fix yet. Write the solution, explain its risks and say how you would stop that workaround sitting in the repository for ten years.
Solutions
Solution 1. What happened: npm install is not obliged to respect the lockfile. If the package.json declares ^ ranges and new versions were published over the weekend, npm install can resolve them and install a different tree from the recorded one, rewriting the lockfile inside the runner — where nobody sees the diff. All it takes is for one dependency, direct or transitive, to have published a minor version with a behaviour change for the suite to fail. The lockfile did not prevent it because it was there, but it was not being used as the rule: it described a tree the command was under no obligation to reproduce. It is exactly the parseISO incident scenario. The two changes: (1) replace npm install with npm ci in every job, which installs the lockfile's tree without deviating and fails if the lockfile does not match the package.json; and (2) channel updates through a pull request with Dependabot, so that every change to the tree is a reviewable commit, with its own CI and with the lockfile diff in front of you. A third complementary change, cheaper than it sounds: pin .nvmrc and the base image too, because a Node version change produces the same symptom — "red without touching anything" — and gets diagnosed just as badly.
Solution 2. The figure that governs the answer is the quality of the suite: with 45% coverage and no integration tests, CI is not a sufficient safety net, and the policy in section 8 — which rests precisely on that net — cannot be copied as it stands. Changes relative to the table: remove auto-merge for production entirely, patches included; keep auto-merge only for development dependencies, whose failure shows up in the pipeline itself and not in production; group everything into a single weekly PR so the review fits into the real time of a team of four; and reduce the frequency to fortnightly if the team cannot keep up, consciously accepting the delay instead of accumulating ignored PRs.
What I would do first: not touch the bot's configuration, but the test suite. Specifically, write integration tests over the three or four critical business paths, which is what turns a decorative CI into a gate. Automating dependencies with a net full of holes is swapping a slow, visible risk for a fast, invisible one. In the meantime, a cheap and highly effective measure: enable Dependabot for security alerts only, which are the ones that cannot be postponed, and leave the maintenance ones until the net exists.
Solution 3. The solution is an override in the root package.json:
The nested form is used rather than the global one on purpose: it limits the replacement to some-linter's subtree, so that if another package depends on an older semver for a legitimate reason, it is not affected. The global form would be more convenient and riskier. Risks: you are running some-linter with a version of semver its maintainer has not tested; if 7.6.2 changed some subtle behaviour, the failure will show up inside a dependency, which is the worst place to diagnose it. On top of that the override is silent: it does not start failing when it stops being necessary, it simply sits there pinning an ever older version and blocking future updates of that entire subtree.
How to stop it fossilising, with three measures of almost no cost: a comment next to the entry with the date, the reason and a link to the issue opened on some-linter; a quarterly review of the full overrides list within the dependency rota from section 8; and, the most effective, subscribing to the issue, so that when the maintainer publishes the fix a notification arrives and the workaround is removed the same day. An override with no expiry date is indistinguishable from a permanent decision nobody made.
Conclusion
The blind spot is no longer blind. Reservalia knows that 418 packages from around 330 maintainers travel inside reservalia/api:a3f9c21, and it knows how to see them, pin them and update them. It is clear about what a lockfile guarantees — version, origin and hash — and what it does not — availability, environment, the complete build — and that is why it accompanies it with .nvmrc and a pinned base image. It uses npm ci in the pipeline, so an inconsistent lockfile fails in twenty seconds instead of quietly installing something nobody reviewed. It knows that a lockfile is regenerated and never edited by hand, and it knows the equivalent of the pattern in Poetry, Gradle, Go, Cargo and Composer. It keeps its ranges at ^ so fixes can arrive, relying on the lockfile to decide what actually gets installed. And it sees its 1,124 transitive dependencies with npm ls and npm why, using overrides with a comment and a review date only when there is no alternative. Above all, updates have stopped being a traumatic annual event and become a flow: a .github/dependabot.yml with grouping, a PR limit and a sensible schedule; a written policy separating what merges itself from what requires reading a changelog; a weekly rota with a name and a bounded half hour; and a cache whose key is the lockfile hash, so it invalidates itself and never produces a false green. The parseISO incident that was polluting the change failure rate now has two defences: human review of production minors and a test covering the case that broke.
One flank remains uncovered, and it is the one that turns all of the above into a security problem and not merely a maintenance one. Those 1,147 dependencies are downloaded from a public registry, executed with the pipeline's permissions and end up inside an image that is deployed to production; and the pipeline itself, meanwhile, holds credentials over AWS, runs third-party actions pinned by tag and publishes the artifacts everybody trusts. The lesson Security in CI/CD tackles both halves of the problem — the security of the software passing through the pipeline and the security of the pipeline itself — adds a security job to Reservalia with a severity policy the team can actually sustain, and finishes where this chain of trust ends: the signed inventory of what each artifact contains.
CI/CD Course: Continuous Integration and Deployment
Module 1: Introduction to CI/CD
- Basic CI/CD Concepts
- Benefits of CI/CD
- Popular CI/CD Tools
- The Course Project: the Application We Are Going to Automate
- DORA Metrics: How Software Delivery Is Measured
Module 2: Continuous Integration (CI)
- Introduction to Continuous Integration
- Setting Up a CI Environment
- Build Automation
- Automated Testing
- Code Quality and Static Analysis
- Artifacts, Versioning and Promotion
- Integration with Version Control
Module 3: Continuous Deployment (CD)
- Introduction to Continuous Deployment
- Deployment Automation
- Infrastructure as Code and Reproducible Environments
- Deployment Strategies
- Feature Flags, Rollback and Failure Recovery
- Monitoring and Feedback
Module 4: Advanced CI/CD Practices
- CI/CD Pipelines
- Dependency Management
- Security in CI/CD
- Scalability and Performance
- Pipeline as Code: Templates, Reuse and Testing the Pipeline
- Databases in the Pipeline: Safe Migrations
Module 5: Implementing CI/CD in Real Projects
- Case Study: Web Project
- Case Study: Mobile Application
- Case Study: Microservices
- Case Study: Modernising a Legacy Project
Module 6: Tools and Technologies
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker and Kubernetes
- GitHub Actions in Depth
- Comparison and Criteria for Choosing a Tool
Module 7: Practical Exercises
- Exercise 1: Setting Up a Basic Pipeline
- Exercise 2: Integrating Automated Tests
- Exercise 3: Deploying to a Production Environment
- Exercise 4: Monitoring and Feedback
- Exercise 5: Hardening the Pipeline with Security and Secrets
- Final Project: A Complete End-to-End Pipeline
