CI/CD & Déploiement

L'essentiel pour ton projet SOSLock

Bootcode KIRI-S04 — Semaine 3

Objectifs de la leçon

1. Automatiser les tests avec CI

Chaque push = lint + test + build automatique

2. Dockeriser SOSLock

Backend Express/TS + Frontend React/Vite

3. Déployer en production

VPS + Docker Compose + GitHub Actions

4. Gérer les secrets

Jamais dans le code, toujours dans GitHub Secrets

Plan du cours

1

CI/CD : Les concepts essentiels

Intégration continue, livraison continue, déploiement continu

2

GitHub Actions : Ton premier pipeline CI

Workflows YAML, triggers, jobs, secrets, cache

3

Dockeriser SOSLock

Dockerfile backend + frontend multi-stage + Compose production

4

Déployer sur un VPS

GHCR, SSH deploy, variables d'environnement

5

Pièges courants

Les erreurs qui font perdre des heures

Le problème

Tu déploies SOSLock à la main ?

1️⃣

SSH sur le serveur

Manuel, erreur de mot de passe

2️⃣

git pull + npm build

Tu oublies npm install ?

3️⃣

Redémarrer le serveur

Et si les tests passent pas ?

Résultat : déploiements lents, erreurs fréquentes, impossible de revenir en arrière

CI vs CD : Les 3 concepts

CI — Intégration Continue

Chaque push déclenche automatiquement lint + tests + build

On détecte les erreurs immédiatement, pas en production

CD — Livraison Continue

Le code est prêt à déployer après le CI (artefact = image Docker)

Tu décides QUAND déployer — ce n'est pas automatique

CD — Déploiement Continu

Le code est déployé automatiquement en production après le CI

Risqué pour SOSLock au début — préfère la livraison continue

GitHub Actions

Automatise tout, directement dans ton repo

Un workflow = un fichier YAML dans .github/workflows/

C'est du code versionné comme le reste de ton projet

Structure d'un workflow

name: CI SOSLock

on:

push:

branches: [main]

pull_request:

branches: [main]

jobs:

test:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

with: { node-version: 20 }

- run: npm ci

- run: npm run lint

- run: npm test

- run: npm run build

on

Quand déclencher

jobs

Groupes d'étapes

steps

Actions unitaires

uses/run

Réutiliser / Exécuter

CI pour SOSLock : Backend + Frontend

soslock-api (Express/TS)

jobs:

backend:

runs-on: ubuntu-latest

defaults:

working-directory: ./backend

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

- run: npm ci

- run: npm run lint

- run: npm test

- run: npm run build

soslock-frontend (React/Vite)

jobs:

frontend:

runs-on: ubuntu-latest

defaults:

working-directory: ./frontend

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

- run: npm ci

- run: npm run lint

- run: npm run build

Les 2 jobs tournent en parallèle — gain de temps !

Les secrets dans GitHub

❌ JAMAIS ça

env:

DATABASE_URL: "postgres://admin:pass123@db:5432/soslock"

Mot de passe visible dans le code Git !

✅ Toujours ça

env:

DATABASE_URL: ${{{ secrets.DATABASE_URL }}}

GitHub Secrets injecte la valeur automatiquement

Configuration : Settings → Secrets and variables → Actions → New repository secret

Pour SOSLock : DATABASE_URL, JWT_SECRET, SSH_PRIVATE_KEY, VPS_HOST

Le cache npm : 60s → 5s

# Ajoute ça dans chaque job, avant npm ci

- name: Cache npm

uses: actions/cache@v4

with:

path: ~/.npm

key: npm-${{{ hashFiles('**/package-lock.json') }}}

restore-keys: npm-

Sans cache

~60s

npm ci télécharge tout à chaque run

Avec cache

~5s

Les dépendances sont restaurées depuis le cache

Dockeriser SOSLock

Backend Express/TS + Frontend React/Vite

Pourquoi Docker ?

Même environnement partout : dev = staging = production

Plus de "ça marche chez moi"

Dockerfile : Backend SOSLock

# backend/Dockerfile

FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM node:20-alpine AS runner

WORKDIR /app

COPY --from=builder /app/dist ./dist

COPY --from=builder /app/node_modules ./node_modules

COPY --from=builder /app/package.json ./

EXPOSE 3000

CMD ["node", "dist/server.js"]

Multi-stage : le builder compile TypeScript, le runner ne contient que le nécessaire pour exécuter

Dockerfile : Frontend SOSLock

# frontend/Dockerfile

FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html

COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

nginx sert les fichiers statiques du build Vite — pas besoin de Node.js en production

Docker Compose : SOSLock en production

services:

backend:

image: ghcr.io/you/soslock-api:latest

restart: unless-stopped

depends_on:

db:

condition: service_healthy

env_file: .env

healthcheck:

test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]

frontend:

image: ghcr.io/you/soslock-frontend:latest

restart: unless-stopped

ports:

- "80:80"

db:

image: postgres:16-alpine

restart: unless-stopped

volumes:

- pgdata:/var/lib/postgresql/data

healthcheck:

test: ["CMD-SHELL", "pg_isready -U soslock"]

Docker Compose : Bonnes pratiques

restart: unless-stopped

Redémarre automatiquement si le conteneur crash ou si le serveur reboot

healthcheck

Docker sait si le service est vraiment healthy, pas juste "en cours d'exécution"

depends_on + condition

Le backend attend que PostgreSQL soit prêt avant de démarrer

volumes pour la DB

Les données PostgreSQL survivent au redémarrage des conteneurs

Déployer sur un VPS

Ton serveur dédié pour SOSLock

1️⃣ Acheter un VPS (Hetzner, OVH, DigitalOcean...)

2️⃣ SSH + installer Docker + Docker Compose

3️⃣ Clé SSH dédiée pour le déploiement automatique

Pipeline CD complet pour SOSLock

name: Deploy SOSLock

on:

push:

branches: [main]

jobs:

deploy:

needs: [backend, frontend] # attend le CI

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

# 1) Build & push les images Docker

- uses: docker/login-action@v3

with:

registry: ghcr.io

token: ${{{ secrets.GITHUB_TOKEN }}}

- run: docker build -t ghcr.io/you/soslock-api:latest ./backend

- run: docker push ghcr.io/you/soslock-api:latest

- run: docker build -t ghcr.io/you/soslock-frontend:latest ./frontend

- run: docker push ghcr.io/you/soslock-frontend:latest

# 2) SSH deploy sur le VPS

- uses: appleboy/ssh-action@v1

with:

host: ${{{ secrets.VPS_HOST }}}

key: ${{{ secrets.SSH_PRIVATE_KEY }}}

script: |

cd /opt/soslock

docker compose pull

docker compose up -d

Variables d'environnement en production

❌ .env dans Git

# .env commité — DANGER !

DATABASE_URL=postgres://admin:pass@db:5432/soslock

JWT_SECRET=my-super-secret-key

Tout le monde a accès à tes secrets !

✅ .env sur le serveur uniquement

# Sur le VPS : /opt/soslock/.env

DATABASE_URL=postgres://prod_user:xxx@db:5432/soslock

JWT_SECRET=a8f3b2c1d4...

Le fichier .env n'existe que sur le serveur

# .gitignore — TOUJOURS

.env

.env.production

Pièges courants

❌ Toujours :latest

Impossible de rollback si tu n'as pas de tag de version

✅ Tag par commit SHA

soslock-api:abc1234 — tu peux revenir à n'importe quelle version

❌ Oublier docker compose pull

L'ancienne image est réutilisée depuis le cache local

✅ pull avant up

docker compose pull && docker compose up -d

❌ Pas de healthcheck

Docker croit que le conteneur est healthy alors que l'app a crashé

✅ healthcheck sur chaque service

wget --spider /health pour le backend, pg_isready pour la DB

❌ YAML mal indenté

Une erreur d'espace casse tout le workflow GitHub Actions

✅ Valide ton YAML

Utilise yamllint ou le validateur intégré de GitHub

À retenir !

CI = filet de sécurité

Chaque push = lint + test + build automatique

Docker = même environnement

Multi-stage build pour des images légères

Compose = orchestration

restart + healthcheck + depends_on pour la prod

Secrets = jamais dans Git

GitHub Secrets + .env sur le serveur uniquement

SOSLock en production : push → CI → build Docker → deploy VPS

Tout automatique, tout reproductible, tout sécurisé