Els cursos de CI/CD solen fallar en el mateix punt: ensenyen pipelines sobre un "hola món" que no té base de dades, ni migracions, ni dues aplicacions que comparteixin codi, ni un equip amb opinions diferents. I després, en un projecte real, no encaixa res. Aquest curs fa el contrari: construirem, pas a pas, el pipeline complet d'una aplicació amb la complexitat justa perquè apareguin els problemes de debò. Aquesta aplicació es diu Reservalia. En aquesta lliçó la coneixerem a fons: què fa el producte, qui forma l'equip, com està organitzat el repositori, quines ordres existeixen ja —perquè el pipeline no inventa res, només invoca el que el projecte ja sap fer—, quins entorns hi ha i a quina infraestructura despleguem. Acabarem amb el full de ruta del curs aplicat a Reservalia i amb el que necessites instal·lat per seguir-lo. Encara no escriurem cap flux de treball: la primera peça real de pipeline es construeix a la lliçó 02-02.

Contingut

  1. Què és Reservalia
  2. L'equip: Marta, Diego i Nuria
  3. Com treballen avui (i per què fa mal)
  4. L'estructura del repositori
  5. Els package.json i les ordres que el pipeline invocarà
  6. L'entorn de desenvolupament local amb Docker Compose
  7. Els tres entorns: dev, staging i prod
  8. La infraestructura AWS de destinació
  9. El full de ruta del curs aplicat a Reservalia
  10. Com seguir el curs si no fas servir Node.js
  11. Què necessites instal·lat
  12. Errors comuns i consells
  13. Exercicis
  14. Conclusió

  1. Què és Reservalia

Reservalia és una plataforma SaaS de reserva de cites per a petits negocis de serveis: perruqueries, clíniques dentals, tallers mecànics, fisioterapeutes, centres d'estètica. És una empresa fictícia creada per a aquest curs; qualsevol semblança amb la realitat és intencionada però no real.

El producte té dues cares:

  • El panell del negoci. El propietari de la perruqueria configura els seus serveis (tall, color, tractament), els seus professionals, els horaris d'obertura i les seves vacances. Consulta l'agenda del dia, confirma cites i veu estadístiques bàsiques.
  • La pàgina pública de reserva. El client final entra des d'un enllaç, veu els forats lliures, en tria un i reserva sense registrar-se. Rep una confirmació per correu i un recordatori 24 hores abans.

Model de negoci: subscripció mensual per negoci, amb tres plans. Ara mateix tenen 340 negocis de pagament i processen unes 9.000 cites al mes.

1.1. Per què Reservalia és un bon projecte per aprendre CI/CD

No és un exemple de joguina, i això importa. Reservalia té exactament els ingredients que fan interessant un pipeline:

Ingredient Per què complica el pipeline On el tractarem
Base de dades amb esquema evolutiu Hi ha migracions per aplicar, i aplicar-les malament trenca producció o perd dades 04-06
Dues aplicacions en un mateix repositori Es prova tot a cada canvi o només el que està afectat? 04-04
Codi compartit entre elles Un canvi en tipus compartits afecta totes dues 02-03
Dades sensibles (noms, telèfons, correus de clients finals) Els entorns no poden compartir dades reals; cal gestionar secrets 04-03
Horari comercial amb pics No es pot desplegar de qualsevol manera a les 11:00 d'un dissabte 03-04
Tercers integrats (correu, passarel·la de pagament) Cal decidir què se simula en proves i què no 02-04
Una aplicació mòbil (a partir del mòdul 5) El "desplegament" passa per botigues d'aplicacions, amb les seves regles 05-02

Si aconsegueixes automatitzar Reservalia, sabràs automatitzar el teu projecte.

  1. L'equip: Marta, Diego i Nuria

Tres persones, tres punts de vista. Tots tres apareixeran durant tot el curs, i les seves discussions són les que probablement tindràs tu amb el teu equip.

2.1. Marta — tech lead

Fa quatre anys que és a Reservalia i arrossega la responsabilitat que el producte funcioni. És qui decideix què es desplega i quan, i qui rep la trucada del comercial quan un client es queixa.

  • Què li preocupa: que les incidències no es repeteixin i poder justificar davant de direcció el temps que l'equip dedica a "coses que no són funcionalitats".
  • El seu paper al curs: aporta el punt de vista de negoci. És qui pregunta "quant costa això i què hi guanyem?" (la lliçó 01-02) i qui prémerà el botó d'aprovació a la porta manual del desplegament a producció.
  • La seva frase: "Prefereixo desplegar deu vegades al dia i que cada desplegament m'importi ben poc, que desplegar un cop a la setmana i no dormir el dijous."

2.2. Diego — desenvolupador backend

Tres anys a l'empresa. Escriu la major part d'apps/api i és, avui, l'única persona que sap desplegar. Sap perfectament que el procés actual és dolent; el que no ha tingut mai és temps per arreglar-lo.

  • Què li preocupa: que el pipeline el faci anar més lent. Li fa pànic un CI que trigui 40 minuts a dir-li si el seu canvi està bé.
  • El seu paper al curs: és l'escèptic productiu. Cada vegada que afegim un pas al pipeline, en Diego preguntarà quant temps hi suma. Gràcies a ell, el pipeline final serà ràpid.
  • La seva frase: "Si el CI triga més que anar a buscar un cafè, acabaré ignorant-lo."

2.3. Nuria — SRE

Es va incorporar fa vuit mesos, a mitja jornada compartida amb un altre producte. Va muntar la infraestructura d'AWS a mà, des de la consola web, perquè calia sortir a producció ja. Porta les guàrdies.

  • Què li preocupa: que la infraestructura no estigui documentada enlloc tret del seu cap, i que un rollback avui signifiqui recompilar i tornar a pujar per SFTP amb clients esperant.
  • El seu paper al curs: aporta la perspectiva d'operació. És qui insisteix en la infraestructura com a codi (03-03), en el rollback (03-05) i en el monitoratge (03-06).
  • La seva frase: "Un desplegament que no es pot desfer en cinc minuts no és un desplegament, és una aposta."

  1. Com treballen avui (i per què fa mal)

Recordem el punt de partida, ara amb el detall complet del procés:

# El ritual del divendres a Reservalia. Durada: ~3 hores.
# Executor: en Diego, sempre en Diego.

# 1) Es baixa l ultim de main (sense saber exactament que hi entra)
git checkout main && git pull

# 2) Compila al seu portatil, amb LA SEVA versio de Node i ELS SEUS node_modules
cd apps/api  && npm install && npm run build
cd ../web    && npm install && npm run build

# 3) Puja els fitxers per SFTP a l unic servidor de produccio
sftp diego@reservalia-prod
#   > put -r apps/api/dist/*  /var/www/api/
#   > put -r apps/web/dist/*  /var/www/web/

# 4) Reinicia el proces a ma
ssh diego@reservalia-prod 'pm2 restart api'

# 5) Aplica les migracions enganxant SQL a psql
psql -h reservalia-prod-db -U admin -d reservalia
#   > ALTER TABLE cites ADD COLUMN recordatori_enviat boolean DEFAULT false;

# 6) Comprova a ull que la web carrega i crea una cita de prova

# 7) Escriu al canal de l equip: "desplegat ✅"

I els problemes d'aquest ritual, enumerats sense pietat:

# Problema Conseqüència real que ja han patit
1 Build al portàtil d'en Diego La build no és reproduïble; ningú no pot recrear el que hi ha en producció
2 npm install en comptes de npm ci Una versió menor d'una dependència va entrar en producció sense que ningú l'hagués provada
3 Desplegament incremental per SFTP Fitxers de versions antigues continuen vius al servidor; l'estat real és desconegut
4 Finestra de desplegament sense servei Hi ha uns segons en què l'API respon a mitges
5 Migracions a mà Ningú no sap amb certesa quines migracions s'han aplicat ni en quin ordre
6 Sense registre de què es va desplegar Davant d'una fallada, cal endevinar què va canviar
7 Verificació visual Es va donar per bo un desplegament en què l'enviament de correus estava trencat
8 Bus factor d'1 A les vacances d'en Diego a l'agost, no es va desplegar res durant tres setmanes
9 Rollback ≈ 1 hora A l'última incidència, el servei va estar degradat 40 minuts
10 Divendres a la tarda Dos caps de setmana espatllats el darrer trimestre

Cada línia d'aquesta taula desapareixerà en algun moment del curs. Guarda-la: a la lliçó final (07-06) la repassarem sencera.

  1. L'estructura del repositori

Reservalia té un únic repositori a github.com/reservalia/reservalia, organitzat com a monorepo amb workspaces d'npm. Aquesta és la seva estructura tal com està avui, abans de començar:

reservalia/
├── .github/
│   └── workflows/              ← buit avui; aquí viuran els nostres pipelines
├── apps/
│   ├── api/                    ← Node.js 20 + TypeScript + Express + PostgreSQL
│   │   ├── src/
│   │   │   ├── index.ts              punt d'entrada del servidor
│   │   │   ├── rutes/
│   │   │   │   ├── cites.ts          crear, llistar i cancel·lar cites
│   │   │   │   ├── negocis.ts        alta i configuració de negocis
│   │   │   │   └── disponibilitat.ts càlcul de forats lliures
│   │   │   ├── domini/
│   │   │   │   ├── cita.ts           regles de negoci d'una cita
│   │   │   │   └── agenda.ts         solapaments, horaris, festius
│   │   │   └── db/
│   │   │       ├── client.ts         connexió a PostgreSQL
│   │   │       └── migracions/
│   │   │           ├── 0001_crear_negocis.sql
│   │   │           ├── 0002_crear_cites.sql
│   │   │           └── 0003_afegir_recordatoris.sql
│   │   ├── tests/
│   │   │   ├── unitat/               ràpids, sense base de dades
│   │   │   └── integracio/           contra PostgreSQL real
│   │   ├── Dockerfile          ← l'escriurem a 02-03
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   └── web/                    ← React + Vite + TypeScript
│       ├── src/
│       │   ├── main.tsx
│       │   ├── pagines/
│       │   │   ├── PanelNegoci.tsx
│       │   │   └── ReservaPublica.tsx
│       │   └── components/
│       ├── tests/
│       ├── Dockerfile          ← l'escriurem a 02-03
│       ├── package.json
│       └── vite.config.ts
│
├── packages/
│   └── tipus-compartits/       ← tipus TypeScript usats per api i web
│       ├── src/index.ts
│       └── package.json
│
├── infra/                      ← infraestructura com a codi (mòdul 3)
│   ├── terraform/
│   │   ├── moduls/
│   │   └── entorns/
│   │       ├── staging/
│   │       └── prod/
│   └── README.md
│
├── docker-compose.yml          ← entorn de desenvolupament local
├── package.json                ← arrel del monorepo, amb workspaces
├── package-lock.json           ← un de sol per a tot el monorepo!
├── .nvmrc                      ← fixa la versió de Node
└── README.md

Tres decisions d'aquesta estructura mereixen explicació, perquè condicionen tot el pipeline:

Monorepo amb un únic package-lock.json. En fer servir workspaces d'npm, hi ha un sol fitxer de bloqueig a l'arrel que governa les dependències d'api, web i tipus-compartits. Avantatge: un únic npm ci instal·la tot de manera coherent i reproduïble. Conseqüència per al pipeline: la instal·lació es fa una vegada a l'arrel, no una per aplicació.

packages/tipus-compartits. Aquí viuen els tipus que l'API i la web comparteixen (per exemple, la forma d'una Cita). És el que garanteix que si en Diego canvia el model de dades, la web deixi de compilar en CI en comptes de trencar-se en producció. També significa que un canvi en aquest paquet obliga a provar les dues aplicacions: és la complicació que fa interessant l'"executar només allò afectat" de la lliçó 04-04.

.nvmrc. Un fitxer d'una línia que diu quina versió de Node fa servir el projecte. És la peça més barata de reproduïbilitat que existeix:

20.11.0

Amb això, el portàtil d'en Diego, el de la Marta i el runner de CI fan servir exactament la mateixa versió. A la lliçó 01-01 vam veure que la versió no fixada és un dels enemics clàssics de la build reproduïble; aquest fitxer ho resol.

  1. Els package.json i les ordres que el pipeline invocarà

Aquí hi ha una idea central del curs, i convé subratllar-la:

El pipeline no inventa res. Només executa, en una màquina neta, les mateixes ordres que tu executes al teu portàtil.

Per això, abans d'escriure cap flux de treball, el projecte ha de tenir les seves ordres ben definides. Si npm test no funciona a la teva màquina, no funcionarà en CI. Si funciona a la teva màquina però només perquè tens una variable d'entorn que ningú més no té, fallarà en CI. Un pipeline és, sobretot, un revelador de suposicions ocultes.

5.1. package.json de l'arrel

{
  "name": "reservalia",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "engines": {
    "node": ">=20.11.0 <21"
  },
  "scripts": {
    "build":     "npm run build --workspaces --if-present",
    "test":      "npm run test --workspaces --if-present",
    "lint":      "npm run lint --workspaces --if-present",
    "typecheck": "npm run typecheck --workspaces --if-present",
    "dev":       "docker compose up -d && npm run dev --workspace apps/api"
  }
}

Punt per punt, perquè cada línia té un motiu:

  • "private": true evita que el paquet arrel es publiqui per accident al registre públic d'npm. En un monorepo és obligatori.
  • "workspaces" declara els subprojectes. En executar npm ci a l'arrel, npm instal·la les dependències de tots ells i crea els enllaços entre tipus-compartits i les aplicacions que el fan servir.
  • "engines" documenta la versió de Node admesa. Combinat amb .nvmrc, deixa constància explícita del requisit.
  • --workspaces --if-present executa l'script a cada subprojecte que el tingui definit, i no falla en els que no el tinguin. És el que permet que un sol npm test a l'arrel provi l'API, la web i el paquet compartit.

Amb això, el pipeline sencer es podria reduir a quatre ordres. Comprova-ho:

npm ci             # installacio reproduible de tot el monorepo
npm run lint       # estil i errors evidents
npm run typecheck  # coherencia de tipus entre api, web i tipus-compartits
npm run test       # proves de tots els subprojectes
npm run build      # artefactes compilats

Aquestes cinc ordres són, literalment, l'esquelet del pipeline que construirem al mòdul 2.

5.2. package.json d'apps/api

{
  "name": "@reservalia/api",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev":             "tsx watch src/index.ts",
    "build":           "tsc --project tsconfig.build.json",
    "start":           "node dist/index.js",
    "test":            "vitest run",
    "test:unitat":     "vitest run tests/unitat",
    "test:integracio": "vitest run tests/integracio",
    "lint":            "eslint src tests --max-warnings 0",
    "typecheck":       "tsc --noEmit",
    "migrate":         "node dist/db/migrar.js",
    "migrate:status":  "node dist/db/migrar.js --status"
  },
  "dependencies": {
    "@reservalia/tipus-compartits": "*",
    "express": "4.19.2",
    "pg": "8.11.5",
    "zod": "3.23.8"
  },
  "devDependencies": {
    "@types/express": "4.17.21",
    "eslint": "8.57.0",
    "tsx": "4.7.1",
    "typescript": "5.4.5",
    "vitest": "1.6.0"
  }
}

Detalls importants per al pipeline:

  • test:unitat i test:integracio estan separats. Les unitàries són ràpides i no necessiten base de dades; les d'integració necessiten un PostgreSQL aixecat. Aquesta separació és la que ens permetrà, a 02-04, donar retroalimentació ràpida a en Diego: primer les ràpides, i només si passen, les lentes.
  • --max-warnings 0 al lint. Sense això, ESLint acaba amb codi de sortida 0 encara que hi hagi avisos, i el pipeline es posaria verd amb problemes a dins. Recorda de 01-03: el pipeline només entén de codis de sortida.
  • typecheck separat de build. tsc --noEmit comprova els tipus sense generar fitxers. És ràpid i pot córrer en paral·lel amb els tests.
  • migrate existeix com a script. Avui en Diego enganxa SQL a mà; el projecte ja té l'ordre, només que ningú no la fa servir. A la lliçó 04-06 la integrarem al desplegament.
  • Versions exactes, sense ^. "express": "4.19.2" i no "^4.19.2". Juntament amb npm ci, garanteix que dues instal·lacions donin el mateix. L'estratègia completa de dependències és matèria de 04-02.

5.3. package.json d'apps/web

{
  "name": "@reservalia/web",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev":       "vite",
    "build":     "vite build",
    "preview":   "vite preview",
    "test":      "vitest run",
    "lint":      "eslint src tests --max-warnings 0",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@reservalia/tipus-compartits": "*",
    "react": "18.3.1",
    "react-dom": "18.3.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "4.2.1",
    "typescript": "5.4.5",
    "vite": "5.2.11",
    "vitest": "1.6.0"
  }
}

Fixa't que els noms dels scripts coincideixen amb els de l'API: build, test, lint, typecheck. Aquesta convenció no és cosmètica: és el que fa que npm run test --workspaces funcioni i el que permetrà que el pipeline tracti totes dues aplicacions amb la mateixa lògica.

Consell transferible: unificar els noms dels scripts entre subprojectes és una de les inversions més rendibles abans de muntar un pipeline. Si en un projecte l'ordre és npm test i en un altre npm run test:ci, el teu YAML s'omplirà de casos especials.

  1. L'entorn de desenvolupament local amb Docker Compose

Perquè les proves d'integració funcionin —tant al portàtil com després en CI— cal un PostgreSQL. Reservalia l'aixeca amb Docker Compose:

# docker-compose.yml — entorn de desenvolupament local de Reservalia
services:
  db:
    image: postgres:16.3          # versio FIXADA, igual que a RDS
    container_name: reservalia-db
    environment:
      POSTGRES_USER: reservalia
      POSTGRES_PASSWORD: desenvolupament    # ⚠️ nomes local, mai en un altre entorn
      POSTGRES_DB: reservalia
    ports:
      - "5432:5432"               # accessible des del portatil
    volumes:
      - dades-db:/var/lib/postgresql/data
    healthcheck:                  # esta la base de dades LLESTA, no nomes arrencada?
      test: ["CMD-SHELL", "pg_isready -U reservalia -d reservalia"]
      interval: 5s
      timeout: 3s
      retries: 10

  mailpit:
    image: axllent/mailpit:v1.18   # captura els correus en local
    ports:
      - "1025:1025"                # l SMTP al qual apunta l API
      - "8025:8025"                # interficie web per llegir-los

volumes:
  dades-db:

Quatre coses per explicar, perquè totes reapareixeran al pipeline:

La versió està fixada: postgres:16.3. No postgres:latest. Si en local desenvolupes contra PostgreSQL 16 i producció corre 15, tard o d'hora una consulta funcionarà a la teva màquina i fallarà en producció. La versió de la base de dades és part de la reproduïbilitat.

El healthcheck és la línia més important del fitxer. Un contenidor de PostgreSQL "arrencat" no significa "llest per acceptar connexions": hi ha uns segons d'inicialització. Sense healthcheck, les proves d'integració fallen de manera intermitent perquè la base de dades encara no accepta connexions. I això és exactament una prova flaky, el verí del qual parlàvem a 01-02. La causa número u de flakiness en CI és no esperar que els serveis estiguin realment llestos.

Mailpit substitueix el proveïdor de correu real. Reservalia envia confirmacions i recordatoris. En local i en proves, aquests correus no han de sortir a internet: Mailpit els captura i els mostra en una web. La regla general que aplicarem a 02-04: en proves, cap servei extern real.

La contrasenya desenvolupament està escrita en clar, i està bé. Perquè és una base de dades local, efímera i sense dades reals. A staging i prod no ho farem mai: allà les credencials vénen d'un gestor de secrets. Distingir quan un valor és un secret i quan no és part de la matèria de 04-03.

El flux de treball diari d'en Diego, en quatre ordres:

git clone https://github.com/reservalia/reservalia.git
cd reservalia

nvm use              # llegeix .nvmrc → Node 20.11.0
npm ci               # installa tot el monorepo de manera reproduible
docker compose up -d # aixeca PostgreSQL i Mailpit
npm run dev          # arrenca l API en mode desenvolupament

Quan al mòdul 2 configurem el CI, veuràs que el pipeline fa exactament el mateix: preparar Node, instal·lar dependències, aixecar els serveis necessaris i executar ordres. No hi ha màgia.

  1. Els tres entorns: dev, staging i prod

Un entorn (definit a 01-01) és una instància desplegada i executable del sistema, amb la seva pròpia configuració i les seves pròpies dades. Reservalia en tindrà tres:

Aspecte dev staging prod
Per a què serveix Que l'equip provi canvis en un entorn compartit i real Assaig general: la darrera verificació abans de producció Els clients de debò
Qui el fa servir Marta, Diego, Nuria L'equip, abans d'aprovar un desplegament 340 negocis i els seus clients
Què s'hi desplega Cada merge a main, automàticament Cada merge a main, després de passar dev Només després d'aprovació manual de la Marta
Dades Dades fictícies generades, es poden esborrar Dades fictícies amb volum realista Dades reals de clients
Base de dades RDS db.t4g.micro RDS db.t4g.micro RDS db.t4g.medium, multi-AZ i amb còpies de seguretat
Instàncies de l'API 1 1 2 com a mínim, amb escalat automàtic
Correu Capturat, no surt Capturat, no surt Proveïdor real
Passarel·la de pagament Mode simulat Mode simulat Mode real
Qui hi pot accedir Només l'equip Només l'equip Públic
Si cau No passa res No passa res Incident

Dos principis que regeixen els tres entorns i que convé interioritzar ja:

Principi 1: el mateix artefacte als tres. És la regla de promoció de 01-01. La imatge reservalia/api:a3f9c21 que es prova a dev és exactament la mateixa que arriba a staging i a prod. L'única cosa que canvia entre entorns és la configuració injectada per fora:

# dev
DATABASE_URL=postgres://[email protected]:5432/reservalia
LOG_LEVEL=debug
PASSARELLA_PAGAMENT_MODE=simulat
SMTP_HOST=mailpit.intern

# prod
DATABASE_URL=postgres://[email protected]:5432/reservalia
LOG_LEVEL=info
PASSARELLA_PAGAMENT_MODE=real
SMTP_HOST=smtp.proveidor-correu.com

Principi 2: les dades de producció no surten mai de producció. Copiar la base de dades de prod a staging "per provar amb dades reals" és una pràctica freqüent i una molt mala idea: Reservalia desa noms, telèfons i correus de clients finals. staging fa servir dades generades amb volum realista, no dades reals.

  1. La infraestructura AWS de destinació

La Nuria va muntar això a mà des de la consola d'AWS. Al mòdul 3 ho passarem a Terraform perquè sigui reproduïble; de moment, aquesta és la foto d'on desplega el pipeline:

flowchart TB
    U["👥 Usuaris<br/>negocis i els seus clients"] --> CF["CloudFront + S3<br/>apps/web estatic"]
    U --> ALB["Application Load Balancer<br/>api.reservalia.com"]

    subgraph AWS["AWS · regio eu-west-1"]
        ALB --> ECS

        subgraph ECS["ECS Fargate · servei reservalia-api"]
            T1["Tasca 1<br/>contenidor api:a3f9c21"]
            T2["Tasca 2<br/>contenidor api:a3f9c21"]
        end

        ECS --> RDS[("RDS PostgreSQL 16<br/>multi-AZ en prod")]
        ECS --> SM["Secrets Manager<br/>credencials de BD i APIs"]
        ECS --> CW["CloudWatch Logs"]

        ECR[("ECR<br/>registre d imatges")] -.->|"descarrega la imatge<br/>en desplegar"| ECS
    end

    GHA["⚙️ GitHub Actions"] -->|"1· publica la imatge"| ECR
    GHA -->|"2· actualitza el servei"| ECS
    GHA -->|"3· puja els estatics"| CF

    style ECR fill:#d9f2d9
    style GHA fill:#cfe8ff

Què fa cada peça i per què hi és:

Peça Funció Per què aquesta i no una altra
ECR (Elastic Container Registry) Desa les imatges Docker etiquetades amb el SHA del commit És el registre d'artefactes: la peça que fa possible construir una vegada i promocionar
ECS Fargate Executa els contenidors de l'API sense administrar servidors Orquestració sense la complexitat de Kubernetes: proporcional a un equip de tres persones (vegeu 01-03)
ALB (Application Load Balancer) Reparteix el trànsit entre les tasques i comprova la seva salut Permet desplegaments sense tall: retira una tasca vella només quan la nova respon correctament
RDS PostgreSQL La base de dades gestionada Còpies de seguretat, pedaços i alta disponibilitat sense feina manual
S3 + CloudFront Serveixen la web compilada com a fitxers estàtics La web de Vite és HTML, CSS i JS: no necessita servidor
Secrets Manager Desa credencials i les injecta en temps d'execució Els secrets no viatgen a la imatge ni al repositori (04-03)
CloudWatch Logs Recull els registres (log) de les tasques Base per al monitoratge de 03-06

I el recorregut complet d'un canvi, del portàtil d'en Diego al client final:

sequenceDiagram
    participant D as Diego
    participant GH as GitHub
    participant GA as GitHub Actions
    participant ECR as ECR
    participant ECS as ECS Fargate
    participant U as Usuari

    D->>GH: push a branca + pull request
    GH->>GA: trigger del pipeline
    GA->>GA: npm ci · lint · typecheck · test
    GA-->>GH: ✅ verd, es pot fusionar
    D->>GH: merge a main
    GH->>GA: trigger del pipeline de desplegament
    GA->>GA: docker build → imatge etiquetada amb el SHA
    GA->>ECR: push de reservalia/api:a3f9c21
    GA->>ECS: desplegar a dev i staging
    GA->>GA: proves de fum a staging
    GA-->>GH: ⏸️ esperant aprovacio de la Marta
    Note over GA: porta manual = Lliurament Continu
    GA->>ECS: promocionar el MATEIX artefacte a prod
    ECS->>U: nova versio servint transit

Aquest diagrama és l'objectiu del curs. En acabar el mòdul 3, Reservalia tindrà exactament aquest flux funcionant.

  1. El full de ruta del curs aplicat a Reservalia

Què haurà canviat a Reservalia al final de cada mòdul:

Mòdul Què haurem automatitzat Estat del ritual del divendres
1. Introducció (ets aquí) Res encara: vocabulari, criteris, projecte i mètriques de partida Intacte: 3 h, manual
2. Integració Contínua Cada PR executa npm ci, lint, typecheck, tests unitaris i d'integració contra PostgreSQL. Es construeixen imatges Docker etiquetades amb el SHA i es publiquen a ECR. Es protegeix main perquè res no hi entri en vermell El desplegament continua sent manual, però ja no arriba res trencat a main. En Diego deixa de compilar a cegues
3. Desplegament Continu Desplegament automàtic a dev i staging; porta manual per a prod; infraestructura en Terraform; desplegament progressiu; feature flags; rollback automàtic; monitoratge El ritual del divendres desapareix. Es desplega quan cal, en minuts, i es pot desfer
4. Pràctiques avançades Pipeline reutilitzable i provat; gestió de dependències i actualitzacions; escaneig de seguretat i gestió de secrets; optimització de temps i cost; migracions de base de dades segures i reversibles En Diego deixa de tocar psql per sempre. El pipeline baixa de 14 a menys de 8 minuts
5. Projectes reals S'incorpora apps/mobile (React Native) amb el seu propi flux cap a les botigues; s'estudia la separació en microserveis i com modernitzar un legacy El pipeline admet tres aplicacions amb necessitats diferents
6. Eines El mateix pipeline reexpressat en Jenkins, GitLab CI, CircleCI i Travis CI; contenidors i Kubernetes; GitHub Actions a fons El coneixement de l'equip deixa de dependre d'una eina
7. Exercicis Reconstrucció pràctica de tot, de principi a fi, pel teu compte Ho saps fer tu, no només llegir-ho
8. Recursos Rutes d'aprenentatge, comunitats, certificacions

  1. Com seguir el curs si no fas servir Node.js

Reservalia fa servir Node.js i TypeScript, però el curs no tracta sobre Node.js. Tracta sobre pipelines. Si el teu dia a dia és Python, Java, Go, PHP, Ruby o .NET, tot el que aprenguis s'aplica igual: l'única cosa que canvia són les ordres concretes de cada pas.

Aquí tens la taula de traducció. Guarda-la:

Pas del pipeline Node.js (el curs) Python Java (Maven) Go PHP .NET
Fixar la versió .nvmrc .python-version pom.xml + toolchain go.mod composer.json global.json
Instal·lar dependències npm ci pip install -r requirements.txt mvn dependency:go-offline go mod download composer install --no-dev dotnet restore
Fitxer de bloqueig package-lock.json requirements.txt fixat / poetry.lock pom.xml amb versions exactes go.sum composer.lock packages.lock.json
Anàlisi estàtica eslint ruff / flake8 checkstyle / spotbugs go vet phpstan dotnet format
Comprovació de tipus tsc --noEmit mypy (el compilador) (el compilador) phpstan (el compilador)
Executar proves npm test (vitest) pytest mvn test go test ./... phpunit dotnet test
Construir npm run build (empaquetat o imatge) mvn package go build (imatge) dotnet publish
Artefacte resultant imatge Docker imatge Docker / wheel .jar o imatge binari o imatge imatge imatge
Migracions de BD script migrate alembic upgrade head flyway migrate migrate up doctrine:migrations:migrate dotnet ef database update

El que és idèntic en tots els llenguatges —i és la immensa majoria del curs:

  • Els conceptes: trigger, job, etapa, runner, artefacte, entorn, promoció.
  • El criteri d'èxit o fracàs: el codi de sortida de cada ordre.
  • L'estructura del pipeline: instal·lar → verificar → construir → publicar → desplegar.
  • L'estratègia d'entorns i la regla de construir una vegada i promocionar.
  • Els contenidors: un Dockerfile de Python i un de Node s'assemblen moltíssim.
  • Les estratègies de desplegament, el rollback, els feature flags i el monitoratge.
  • Les mètriques DORA i tot el relatiu a seguretat i secrets.

Suggeriment pràctic: si vols treure'n el màxim partit, adapta cada exercici al teu propi projecte. Quan a 02-02 configurem npm ci && npm test, munta en paral·lel l'equivalent amb pip install && pytest en un repositori teu. Aprendràs el doble.

  1. Què necessites instal·lat

Per llegir el curs i entendre els exemples: res. Tots els fitxers són complets a les lliçons.

Per reproduir els exemples, que és molt recomanable:

Eina Per a què Com comprovar que la tens
Git Clonar, ramificar, fer commits git --version
Compte de GitHub Allotjar el teu repositori i executar Actions (gratis en repositoris públics)
Node.js 20 Executar el projecte d'exemple node --version
Docker + Docker Compose PostgreSQL local i construcció d'imatges docker --version i docker compose version
Un editor amb suport YAML Escriure fluxos de treball sense barallar-te amb la indentació

Opcionals, i només a partir del mòdul 3:

Eina Per a què Nota
Compte d'AWS Desplegar de debò a ECS i RDS Té cost. Pots seguir el mòdul 3 sense ell: tots els fitxers són llegibles i aplicables després
AWS CLI Interactuar amb AWS des de la terminal aws --version
Terraform o OpenTofu Infraestructura com a codi terraform --version

Comprovació ràpida del teu entorn:

# Executa aixo i comprova que no falta res essencial.
echo "--- Imprescindible ---"
git --version            || echo "❌ falta Git"
node --version           || echo "❌ falta Node.js"
docker --version         || echo "❌ falta Docker"
docker compose version   || echo "❌ falta Docker Compose"

echo "--- Opcional (modul 3 endavant) ---"
aws --version            || echo "ℹ️  AWS CLI no installat (opcional)"
terraform --version      || echo "ℹ️  Terraform no installat (opcional)"

Si no pots instal·lar res (per exemple, en un ordinador corporatiu restringit): pots seguir pràcticament tot el curs creant un repositori a GitHub des del navegador i editant els fluxos de treball des de la interfície web. Els runners de GitHub executen al núvol. És menys còmode, però funciona.

Errors Comuns i Consells

Error 1: voler automatitzar abans que les ordres funcionin en local. Si npm test no funciona al teu portàtil, no funcionarà en CI. El pipeline és un revelador de suposicions ocultes: variables d'entorn que només tens tu, serveis que estan arrencats a la teva màquina des de fa setmanes, fitxers que no vas pujar mai al repositori. Primer fes que funcioni en una màquina neta; després automatitza-ho.

Error 2: no fixar les versions de les eines. .nvmrc, engines, postgres:16.3 en comptes de postgres:latest, dependències sense ^. Cada versió no fixada és una build que algun dia deixarà de funcionar sense que ningú hagi tocat res, i aquest és el tipus de fallada més cara de diagnosticar.

Error 3: noms d'scripts diferents a cada subprojecte. Si apps/api fa servir npm test i apps/web fa servir npm run test:ci, el teu pipeline s'omplirà de casos especials. Unifica els noms abans d'escriure el primer flux de treball: és mitja hora de feina que estalvia dies.

Error 4: copiar la base de dades de producció a staging. És temptador ("provem amb dades reals") i és una fuita de dades personals esperant a passar. Reservalia desa telèfons i correus de clients finals. Genera dades fictícies amb volum realista.

Error 5: oblidar el healthcheck dels serveis auxiliars. "Contenidor arrencat" no és "servei llest". És la causa número u de tests intermitents en CI, i com vam veure a 01-02, els tests flaky destrueixen la confiança en el pipeline sencer.

Consell 1: si tens un projecte propi, fes-lo servir en paral·lel. Aplica cada lliçó a Reservalia i també al teu projecte. La transferència de coneixement és molt més gran quan t'enfrontes a les particularitats del teu propi codi.

Consell 2: escriu avui la teva pròpia taula del ritual del divendres. Enumera tots els passos manuals del teu procés de desplegament actual, amb la seva durada. És una llista incòmoda de llegir i és exactament el mapa del que automatitzaràs.

Consell 3: un README.md que funcioni és el primer pas de CI. Si un desenvolupador nou pot clonar el repositori i tenir el projecte funcionant seguint el README sense preguntar a ningú, el teu projecte està llest per automatitzar-se. Si no, arregla això primer: el pipeline és exactament aquell README executat per una màquina.

Exercicis

Exercici 1: deduir el pipeline a partir del projecte

Sense escriure gens de YAML —encara no toca—, i fent servir únicament el que saps del repositori de Reservalia, respon:

  1. En quin ordre executaries npm ci, lint, typecheck, test:unitat, test:integracio i build? Justifica l'ordre.
  2. Quins d'aquests passos es poden executar en paral·lel i quins obligatòriament en sèrie?
  3. Quin pas necessita que hi hagi un PostgreSQL aixecat, i què implica això per al runner?
  4. Un canvi toca només apps/web/src/pagines/PanelNegoci.tsx. Seria correcte executar únicament les proves d'apps/web? I si el canvi toqués packages/tipus-compartits/src/index.ts?

Exercici 2: dissenyar la matriu de configuració per entorn

Reservalia ha de decidir, per a cada valor de configuració, si és un secret (va al gestor de secrets), una variable d'entorn normal (pot ser al repositori) o una cosa que no ha d'existir en aquell entorn. Completa la taula i justifica els tres casos que et semblin menys evidents:

Valor dev staging prod
DATABASE_URL
Contrasenya de la base de dades
LOG_LEVEL
Clau d'API de la passarel·la de pagament
SMTP_HOST
Nom del repositori d'ECR
Clau de signatura dels tokens de sessió

Exercici 3: traduir Reservalia a la teva tecnologia

Imagina't que Reservalia estigués escrita en Python amb FastAPI en comptes de Node.js amb Express. Escriu:

  1. L'estructura equivalent d'apps/api/ (noms de fitxers de configuració i de gestió de dependències).
  2. L'equivalent de les cinc ordres del pipeline (npm ci, lint, typecheck, test, build).
  3. Quines parts del docker-compose.yml canviarien i quines no.
  4. Quines parts del curs deixarien d'aplicar. (Pista: poques.)

Solucions

Solució a l'Exercici 1

1. Ordre i justificació

1. npm ci               ← imprescindible primer: sense dependencies no hi ha res
2. lint  ·  typecheck   ← rapids, detecten errors evidents, sense infraestructura
3. test:unitat          ← rapids, sense base de dades
4. test:integracio      ← lents, requereixen PostgreSQL
5. build                ← nomes te sentit si tot l anterior es verd

El principi que governa aquest ordre s'anomena fail fast: col·loca primer el més ràpid i el que més probablement falli. Si en Diego s'ha deixat un console.log o un tipus incompatible, vols que ho sàpiga en 40 segons, no després d'esperar sis minuts a les proves d'integració. Ordenar el pipeline al revés funciona igual de bé tècnicament, però malbarata el temps de l'equip a cada fallada.

2. Paral·lel davant de sèrie

  • npm ci ha d'anar primer i sol: tot en depèn.
  • lint, typecheck i test:unitat poden anar en paral·lel entre si: són independents, no comparteixen estat i cap no necessita el resultat de l'altre.
  • test:integracio pot anar en paral·lel amb els anteriors si el runner pot aixecar PostgreSQL alhora, encara que se sol executar després per no pagar el cost d'aixecar la base de dades quan alguna cosa trivial ja ha fallat. És una decisió de compromís entre velocitat i cost.
  • build va al final: construir un artefacte de codi que no passa les proves és temps i diners llençats.

Un matís sobre typecheck en aquest monorepo concret: com que apps/web depèn de packages/tipus-compartits, la comprovació de tipus de la web pot requerir que el paquet compartit estigui compilat. En projectes amb referències de projecte de TypeScript això es resol sol; en d'altres, obliga a un build parcial previ. És el tipus de detall que descobreixes en muntar el pipeline i que resoldrem a 02-03.

3. PostgreSQL al runner

test:integracio el necessita. Implicacions per al runner:

  • El runner ha de poder executar contenidors (els runners allotjats de GitHub poden).
  • Cal esperar que la base de dades estigui llesta, no només arrencada: healthcheck o espera activa. Sense això, tests flaky garantits.
  • Cal aplicar les migracions abans d'executar les proves, perquè l'esquema existeixi.
  • Cada execució ha de partir d'una base de dades neta, o l'estat d'una prova contaminarà la següent.

4. Execució selectiva

  • Canvi només a apps/web/src/pagines/PanelNegoci.tsx: sí, seria raonable executar únicament les proves d'apps/web. L'API no pot veure's afectada per un canvi en un component de React. Això és execució selectiva i és una de les principals palanques d'optimització (04-04).
  • Canvi a packages/tipus-compartits/src/index.ts: no. Aquest paquet és una dependència de les dues aplicacions, així que cal provar totes dues. Aquest és precisament el cas que fa perillosa l'execució selectiva mal implementada: si la teva regla és "només provo la carpeta que ha canviat", un canvi al paquet compartit no provaria res i podria trencar les dues aplicacions alhora. L'execució selectiva s'ha de basar en el graf de dependències, no en la ruta del fitxer.

Solució a l'Exercici 2

Valor dev staging prod
DATABASE_URL (sense contrasenya) Variable normal Variable normal Variable normal
Contrasenya de la base de dades Secret Secret Secret
LOG_LEVEL Variable normal (debug) Variable normal (debug) Variable normal (info)
Clau d'API de la passarel·la de pagament Secret (clau de proves) Secret (clau de proves) Secret (clau real)
SMTP_HOST Variable normal Variable normal Variable normal
Nom del repositori d'ECR Variable normal Variable normal Variable normal
Clau de signatura de tokens de sessió Secret Secret Secret, diferent de les altres dues

Els tres casos menys evidents:

DATABASE_URL. La clau està a separar la cadena de connexió de la contrasenya. postgres://[email protected]:5432/reservalia no conté cap secret: és un nom d'amfitrió intern que, sense credencials i sense accés de xarxa, no serveix de res. La contrasenya s'injecta a part. Ficar la contrasenya dins de l'URL és còmode i converteix un valor públic en un secret, amb tot el que això implica: no es pot registrar en logs, no es pot posar al repositori, no es pot mostrar en un missatge d'error.

Clau de la passarel·la de pagament a dev i staging. Encara que siguin claus d'entorn de proves i no moguin diners reals, continuen sent secrets: permeten fer crides en nom de Reservalia, consultar dades i esgotar quotes. Una clau de proves filtrada en un repositori públic és un incident de seguretat menor, però és un incident. Regla pràctica: si el proveïdor l'anomena "clau secreta", és un secret.

Clau de signatura de tokens: diferent a cada entorn. Aquest és el cas més subtil i el més important. Si staging i prod compartissin la clau de signatura, un token emès a staging —on l'equip té accés total i pot crear l'usuari que vulgui— seria vàlid en producció. És una escalada de privilegis de manual. Els secrets criptogràfics no es comparteixen mai entre entorns, ni tan sols "temporalment per provar".

Un criteri general per classificar: si en filtrar-se permet a algú fer una cosa que no hauria de poder fer, és un secret. Si només revela com es diuen les coses, és configuració. Hi tornarem a 04-03.

Solució a l'Exercici 3

1. Estructura equivalent en Python/FastAPI

apps/api/
├── src/
│   ├── main.py                punt d'entrada (en comptes d'index.ts)
│   ├── rutes/
│   ├── domini/
│   └── db/
│       └── migracions/        gestionades per Alembic
├── tests/
│   ├── unitat/
│   └── integracio/
├── pyproject.toml             ← equivalent a package.json
├── poetry.lock                ← equivalent a package-lock.json
├── .python-version            ← equivalent a .nvmrc
├── alembic.ini                configuració de migracions
└── Dockerfile

2. Les cinc ordres

Pas Node.js Python/FastAPI
Instal·lar npm ci poetry install --sync (respecta el lock, com npm ci)
Lint npm run lint ruff check src tests
Tipus npm run typecheck mypy src
Proves npm test pytest
Construir npm run build No hi ha compilació: el "build" és directament docker build
Migracions npm run migrate alembic upgrade head

Una observació interessant: Python no té pas de compilació, així que l'artefacte es produeix directament en construir la imatge Docker. El pipeline té un pas menys, però la resta de l'estructura és idèntica.

3. Canvis al docker-compose.yml

  • No canvia res del servei db: PostgreSQL 16.3, el seu healthcheck, el seu volum i les seves variables són exactament iguals. La base de dades no sap en quin llenguatge està escrita l'aplicació.
  • No canvia res del servei mailpit: captura SMTP, sigui qui sigui l'emissor.
  • Canviaria únicament el servei de l'aplicació, si se n'afegís un: la imatge base (python:3.12-slim en comptes de node:20-slim) i l'ordre d'arrencada (uvicorn src.main:app --reload).

És un bon indicador de quant d'aquest curs és independent del llenguatge: la infraestructura de desenvolupament és pràcticament la mateixa.

4. Quines parts del curs deixarien d'aplicar

Gairebé cap. Concretament:

  • Deixa d'aplicar: les ordres exactes d'npm, la sintaxi de package.json i els detalls de les eines concretes de JavaScript (vitest, ESLint, tsc).
  • Continua aplicant íntegrament: tot el vocabulari de 01-01; els beneficis i costos de 01-02; el mapa d'eines de 01-03; les mètriques DORA de 01-05; tot el mòdul 3 (estratègies de desplegament, IaC, feature flags, rollback, monitoratge); tot el mòdul 4 tret dels exemples de sintaxi; el mòdul 6 complet; i l'estructura, l'ordre i la lògica de cada pipeline que construïm.

Estimació honesta: més del 85 % del curs és independent del llenguatge. El que canvia són les cadenes de text dins dels passos run:.

Conclusió

Ja coneixes el projecte sobre el qual treballarem durant tot el curs:

  • Reservalia és una plataforma SaaS de reserva de cites amb 340 negocis de pagament, dues aplicacions (apps/api en Node.js + Express + PostgreSQL i apps/web en React + Vite), un paquet de tipus compartits i un monorepo amb workspaces d'npm.
  • L'equip són la Marta (tech lead, mira el negoci i el risc), en Diego (backend, vol un CI ràpid i és l'únic que sap desplegar) i la Nuria (SRE, vol infraestructura reproduïble i rollback fiable). Les seves tensions són les que tindràs al teu equip.
  • El projecte ja té les seves ordres definides: npm ci, lint, typecheck, test, build i migrate. Aquesta és la idea clau de la lliçó: el pipeline no inventa res, només executa en una màquina neta el que el projecte ja sap fer. Un pipeline és un revelador de suposicions ocultes.
  • Hi ha tres entornsdev, staging i prod— governats per dos principis: el mateix artefacte es promociona als tres, i les dades de producció no surten mai de producció.
  • La infraestructura de destinació és AWS: imatges a ECR, execució a ECS Fargate darrere d'un balancejador, dades a RDS PostgreSQL, secrets a Secrets Manager i registres (log) a CloudWatch.
  • I saps com seguir el curs amb qualsevol tecnologia: més del 85 % és independent del llenguatge; només canvien les ordres dins de cada pas.

Ens queda una última cosa abans de començar a construir. La Marta demanarà temps de l'equip per muntar tot això, i d'aquí a sis mesos algú preguntarà si ha servit d'alguna cosa. Per respondre a això cal haver mesurat el punt de partida abans de tocar res. A la lliçó següent, Mètriques DORA: Com es Mesura el Lliurament de Programari, veurem les quatre mètriques estàndard del sector —freqüència de desplegament, lead time for changes, change failure rate i time to restore service—, com calcular-les a partir de dades que el mateix pipeline genera, i establirem el quadre de comandament inicial de Reservalia: on són avui i on volen arribar al final del curs.

Curs de CI/CD: Integració i Desplegament Continu

Mòdul 1: Introducció al CI/CD

Mòdul 2: Integració Contínua (CI)

Mòdul 3: Desplegament Continu (CD)

Mòdul 4: Pràctiques Avançades de CI/CD

Mòdul 5: Implementació de CI/CD en Projectes Reals

Mòdul 6: Eines i Tecnologies

Mòdul 7: Exercicis Pràctics

Mòdul 8: Recursos Addicionals

© Copyright 2026. Tots els drets reservats