Retour au blog

Déploiement et DevOps : les leçons dont personne ne m'a prévenu

Variables d'env fausses, pas de plan de rollback, déploiements le vendredi, crashs silencieux. Après des années d'incidents en production, voici les 10 leçons DevOps que j'ai apprises à mes dépens.

4 mars 202612 min de lecture
DevOpsDeploymentCI/CDMonitoringInfrastructure
Déploiement et DevOps : les leçons dont personne ne m'a prévenu
Le pipeline de déploiement - De "ça marche sur ma machine" à la réalité de la prod
Le pipeline de déploiement - De "ça marche sur ma machine" à la réalité de la prod

Mon premier déploiement en production a cassé l'app pendant 3 heures.

J'avais tout testé en local. Le code marchait. Les tests passaient. J'ai cliqué sur déployer, je suis allé chercher un café, et je suis revenu sur un channel Slack en feu.

La chaîne de connexion à la base de données était fausse. Une variable d'environnement. Trois heures de downtime.

C'était la première de nombreuses leçons. Voici ce que j'aurais aimé qu'on me dise.


Leçon 1 : les variables d'environnement vont te trahir

Les variables d'environnement sont la cause #1 de "ça marche sur ma machine."

J'ai vu :

  • Des typos dans les noms de variables (DATABSE_URL au lieu de DATABASE_URL)
  • Des variables manquantes en prod qui existaient en dev
  • Des secrets accidentellement commités sur git
  • Des valeurs différentes entre staging et production

La solution ? Valide au démarrage.

// lib/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  NEXTAUTH_SECRET: z.string().min(32),
  NEXTAUTH_URL: z.string().url(),
});

// Ça s'exécute quand ton app démarre
// Si une variable est manquante ou invalide, l'app crash immédiatement
// Mieux vaut crasher au démarrage qu'en production à 3h du mat
export const env = envSchema.parse(process.env);

Échoue vite. Si une variable est fausse, crash immédiatement. N'attends pas qu'un utilisateur tombe sur la feature cassée.


Leçon 2 : le staging doit correspondre à la production

"Ça marche en staging" veut rien dire si staging ne correspond pas à la production.

J'ai debuggé des problèmes causés par :

  • Différentes versions de Node.js
  • Différentes versions de base de données (PostgreSQL 14 vs 15)
  • Différents OS (Ubuntu vs Alpine)
  • Différentes limites de mémoire
  • Différentes variables d'environnement

La règle : staging devrait être un clone plus petit de la prod, pas un setup différent.

# docker-compose.prod.yml
services:
  app:
    image: node:20-alpine  # Pareil qu'en prod
    environment:
      - NODE_ENV=production  # Pareil qu'en prod
    deploy:
      resources:
        limits:
          memory: 512M  # Mêmes limites qu'en prod

Si tu peux pas te permettre une infrastructure identique, au moins matche :

  • Les versions runtime (Node, Python, etc.)
  • Les versions de base de données
  • L'image OS de base
  • Les variables d'environnement principales

Leçon 3 : ne déploie jamais le vendredi

J'ai appris ça à mes dépens.

Déploiement vendredi 17h. Bug qui apparaît samedi matin. Personne autour. Les clients sont en colère. Je passe mon weekend à réparer au lieu de me reposer.

Maintenant mes règles :

  • Pas de déploiement après jeudi 16h
  • Pas de déploiement avant un jour férié
  • Pas de "quick fix" le vendredi

Si c'est assez urgent pour déployer le vendredi, c'est assez urgent pour avoir l'équipe en standby. Si l'équipe peut pas être en standby, ça peut attendre lundi.


Leçon 4 : chaque déploiement a besoin d'un plan de rollback

"On corrigera en avançant" c'est pas un plan.

Avant chaque déploiement, je demande :

  1. Comment je sais si ce déploiement a échoué ?
  2. Comment je rollback vers la version précédente ?
  3. Combien de temps le rollback prendra ?

Ma méthode : GitHub Releases + Actions.

Chaque release est un tag sur GitHub. Pas besoin de ligne de commande, tu peux tout faire depuis l'interface :

  1. Va dans ReleasesCreate new release
  2. Clique sur Choose a tag → crée un nouveau tag (ex: v1.2.3)
  3. Ajoute un titre et des notes de release (optionnel mais utile)
  4. Clique sur Publish release

Le workflow se déclenche automatiquement :

# .github/workflows/deploy.yml
name: Deploy on Release

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Deploy
        run: npm run deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Quelque chose a cassé ? Rollback en 3 clics :

  1. Va dans l'onglet Actions
  2. Trouve le déploiement de la version précédente qui marchait (ex: v1.2.2)
  3. Clique sur Re-run all jobs

C'est tout. Pas de commandes git à retenir. Pas de stress à 3h du mat.

Pour les migrations de base de données, c'est plus délicat. Ma règle : les migrations doivent être réversibles ou additives.

-- Dangereux : pas de rollback possible
ALTER TABLE users DROP COLUMN old_field;

-- Safe : ajoute d'abord, supprime après
ALTER TABLE users ADD COLUMN new_field VARCHAR(255);
-- Déploie le nouveau code
-- Vérifie que tout marche
-- Plus tard, dans une autre release : DROP COLUMN old_field

Leçon 5 : les logs sont ton meilleur ami (quand ils existent)

La première fois qu'un bug en prod est arrivé, j'avais aucune idée de ce qui s'était passé. Pas de logs. Pas de traces. Juste une page d'erreur vide.

Maintenant je log tout ce qui compte :

// Avant : pas de contexte
console.log('Error');

// Après : information actionnable
logger.error('Payment failed', {
  userId: user.id,
  amount: payment.amount,
  errorCode: error.code,
  errorMessage: error.message,
  timestamp: new Date().toISOString(),
});

Quoi logger :

  • Chaque appel API externe (requête + réponse + durée)
  • Chaque requête base de données qui échoue
  • Chaque tentative d'authentification
  • Chaque transaction de paiement
  • Les actions utilisateur qui modifient des données

Quoi NE PAS logger :

  • Les mots de passe
  • Les numéros de carte bancaire
  • Les données personnelles (RGPD)
  • Les corps de requête complets avec des infos sensibles

Leçon 6 : les health checks préviennent les désastres

Mon app a crashé silencieusement une fois. Le processus tournait, mais il ne répondait pas aux requêtes. Le load balancer continuait d'envoyer du trafic vers une instance morte.

Les health checks règlent ça :

// pages/api/health.ts (Next.js)
export default async function handler(req, res) {
  try {
    // Vérifie la connexion à la base de données
    await db.query('SELECT 1');

    // Vérifie les services externes si critiques
    // await redis.ping();

    res.status(200).json({ status: 'healthy' });
  } catch (error) {
    res.status(500).json({ status: 'unhealthy', error: error.message });
  }
}

Ton load balancer/orchestrateur appelle cet endpoint toutes les 30 secondes. S'il échoue, le trafic est routé ailleurs.


Leçon 7 : les secrets appartiennent à un gestionnaire de secrets

J'ai commité des secrets sur git. Plus d'une fois.

Même si tu les supprimes, ils sont dans l'historique git. Des bots scannent GitHub pour les credentials exposés. Ils trouveront les tiens.

Les règles :

  1. Ajoute .env à .gitignore dès le jour 1
  2. Utilise .env.example avec des valeurs placeholder
  3. Utilise un gestionnaire de secrets pour la prod (Vercel env vars, AWS Secrets Manager, Doppler)
  4. Fais tourner les credentials immédiatement s'ils sont exposés
# .gitignore
.env
.env.local
.env.production

# .env.example (commite ça)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_xxx

Si tu commites accidentellement un secret :

  1. Fais tourner le credential immédiatement
  2. Supprime de l'historique git avec git filter-branch ou BFG
  3. Force push (coordonne avec ton équipe)

Leçon 8 : le monitoring n'est pas optionnel

"L'app est lente" c'est pas actionnable. "Le temps de réponse moyen est passé de 200ms à 2s à 14h32" ça l'est.

Monitoring minimum :

  • Uptime : Est-ce que l'app répond ?
  • Temps de réponse : À quelle vitesse ?
  • Taux d'erreur : Combien de 500 ?
  • Base de données : Pool de connexions, temps de requête
  • Mémoire/CPU : Est-ce qu'on manque de ressources ?

Options gratuites/pas chères qui marchent :

  • Vercel Analytics (si sur Vercel)
  • Sentry (erreurs + performance)
  • Better Stack / Uptime Robot (uptime)
  • PlanetScale insights (base de données)

Configure des alertes. Si le taux d'erreur spike, tu devrais le savoir avant que tes utilisateurs te le disent.


Leçon 9 : le CI/CD vaut le temps de setup

Pendant des mois, je déployais manuellement :

  1. Lance les tests en local
  2. Build en local
  3. SSH sur le serveur
  4. Pull le code
  5. Redémarre l'app

Chaque déploiement prenait 15 minutes de travail manuel. Et je sautais des étapes quand j'étais pressé.

Maintenant tout est automatisé :

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run linter
        run: npm run lint

      - name: Build
        run: npm run build

      - name: Deploy
        run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

Push sur main = déploiement automatique. Les tests échouent = déploiement bloqué. Pas d'étapes manuelles = pas d'erreur humaine.


Leçon 10 : les backups sont inutiles tant que tu les testes pas

"On a des backups" veut rien dire si t'as jamais essayé d'en restaurer un.

Questions à répondre :

  • C'était quand le dernier backup ?
  • Combien de temps prend une restauration ?
  • T'as vraiment essayé de restaurer ?
  • Tu backup aussi les variables d'environnement et les secrets ?

Je planifie un "exercice de reprise après sinistre" tous les trimestres :

  1. Lance un nouvel environnement
  2. Restaure depuis le backup
  3. Vérifie que l'app marche
  4. Documente les problèmes

Le pire moment pour apprendre que tes backups marchent pas, c'est pendant un vrai désastre.


La cheat sheet

LeçonAction
Valide les env varsUtilise Zod, crash au démarrage si invalide
Matche staging à la prodMêmes versions, même OS, mêmes limites
Pas de déploiement le vendrediUrgences seulement, avec l'équipe en standby
Planifie les rollbacksTag les releases, migrations réversibles
Log tout ce qui est utileDu contexte, pas juste "error"
Ajoute des health checksLaisse l'infra détecter les échecs
Utilise des gestionnaires de secretsNe commite jamais .env sur git
Monitore proactivementAlertes avant que les utilisateurs se plaignent
Automatise le CI/CDPas d'étapes de déploiement manuelles
Teste tes backupsExercices de restauration trimestriels

La leçon

Le DevOps c'est pas des outils fancy. C'est ne pas se faire réveiller à 3h du mat.

Chaque leçon ici vient de la douleur. Du downtime. Des utilisateurs en colère. Des sessions de debug le weekend. Des conversations inconfortables avec les managers.

L'objectif est simple : déploie avec confiance, dors paisiblement.


C'est le dernier article de ma série "Ce que j'ai appris à mes dépens". Merci d'avoir lu les 6 parties.

Des questions ? Contacte-moi sur LinkedIn ou découvre plus sur mon blog.