Changer le thème

Stratégies de déploiement GitHub Actions : du VPS aux plateformes cloud

Easton editorial illustration: developer problem-solving desk

Introduction

Trois heures du matin, je fixe les logs GitHub Actions : les erreurs rouges défilent. « Host key verification failed ». Encore SSH.

C’était déjà la cinquième fois que le déploiement échouait. Tout passait en local, puis tout cassait sur GitHub. Frustrant — mais une évidence : le choix de la stratégie de déploiement est bien plus délicat qu’on ne le croit.

VPS auto-géré ou plateforme type Vercel / Cloudflare Pages : chaque option a ses pièges. Mal choisie, vous passerez encore plus de nuits à déboguer.

Cet article passe en revue les principales stratégies GitHub Actions pour trouver celle qui vous convient.


Déploiement VPS par SSH : vieux jeu mais fiable

Au début, je fuyais le déploiement VPS. Trop de détails : clés SSH, known_hosts, options rsync…

Après quelques échecs, cette approche « classique » s’est révélée la plus prévisible.

Clés SSH : ne pas les coder en dur

La question la plus fréquente : où mettre la clé ?

Les débutants collent parfois la clé privée dans le workflow. Erreur. GitHub Secrets est le bon endroit.

Dans le dépôt : Settings → Secrets → Actions, ajoutez SSH_PRIVATE_KEY. Dans le workflow :

- name: Setup SSH
  uses: webfactory/[email protected]
  with:
    ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

Cette action lance ssh-agent et charge la clé. Pratique.

known_hosts : éviter « Host key verification failed »

À la première connexion, SSH demande si vous faites confiance à l’hôte. En CI, pas d’interaction : il faut enregistrer l’empreinte du serveur à l’avance.

Deux approches :

Option 1 : via l’action

- name: Add server to known hosts
  uses: webfactory/[email protected]
  with:
    ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
    known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}

Contenu de SSH_KNOWN_HOSTS :

ssh-keyscan -H your-server.com >> known_hosts.txt
# Copiez le contenu du fichier dans GitHub Secrets

Option 2 : configuration manuelle

- name: Add server to known hosts
  run: |
    mkdir -p ~/.ssh
    ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts

L’option 1 est plus propre ; l’option 2 aide au debug rapide.

rsync ou scp ?

Pour transférer les fichiers, j’utilise rsync :

  • ne transfère que ce qui a changé
  • peut exclure des dossiers (ex. node_modules)
  • synchronisation incrémentale

Exemple typique :

- name: Deploy to server
  run: |
    rsync -avz --delete \
      --exclude 'node_modules' \
      --exclude '.git' \
      ./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/html/

--delete supprime sur la cible les fichiers absents de la source. À manier avec prudence.

Commandes post-déploiement : redémarrer le service

Un site statique : le transfert suffit. Une app Node.js : il faut redémarrer.

J’utilise PM2 pour les processus Node :

- name: Restart application
  run: |
    ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
      "cd /var/www/app && pm2 restart all"

Ou, plus ciblé :

- name: Restart application
  run: |
    ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
      "pm2 restart my-app --update-env"

--update-env recharge les variables d’environnement après un changement de config.


Déploiement cloud : le confort du managé

Le VPS implique de gérer le serveur : correctifs, renouvellement SSL, pare-feu…

Les plateformes managées simplifient tout : push, build, déploiement. Vous codez.

Vercel : référence pour le frontend

Vercel excelle sur Next.js, Astro, React — déploiement quasi sans config.

Attention si vous avez une API backend : les Serverless Functions ont une limite de durée (10 s en gratuit, 60 s en Pro). Au-delà : timeout.

Pour du statique ou une API légère, Vercel suffit. Pour un backend complexe, il faut autre chose.

Workflow GitHub Actions vers Vercel :

name: Deploy to Vercel

on:
  push:
    branches: [main]

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

      - name: Install Vercel CLI
        run: npm i -g vercel@latest

      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

Générez VERCEL_TOKEN dans la console Vercel et stockez-le dans GitHub Secrets.

Cloudflare Pages : quota gratuit généreux

Cloudflare Pages est plus généreux que Vercel en gratuit : bande passante illimitée, 500 builds par mois — largement suffisant pour un projet perso.

Le CDN mondial est rapide ; en test, la latence en Asie m’a souvent paru plus stable qu’avec Vercel.

Workflow :

name: Deploy to Cloudflare Pages

on:
  push:
    branches: [main]

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

      - name: Build
        run: npm run build

      - name: Deploy
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: my-project
          directory: dist

Bonus : R2 offre aussi un bon quota gratuit. Assets statiques sur R2 + CDN Pages = chargement souvent plus rapide.

Netlify : l’acteur historique

Je l’utilise moins que les deux autres, mais l’écosystème est solide.

Configuration proche :

- name: Deploy to Netlify
  uses: netlify/actions/cli@master
  with:
    args: deploy --prod
  env:
    NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
    NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

Le Form handling Netlify traite les soumissions de formulaire — pratique pour une landing simple.

Limites des plateformes managées

Ce n’est pas la solution universelle.

Limites courantes :

  1. Environnement de build contraint : RAM et CPU plafonnés ; gros projets peuvent échouer au build
  2. Peu de personnalisation : modifier nginx ? Impossible
  3. Dépendance à la plateforme : politique ou fermeture → migration
  4. Accès depuis la Chine : certaines plateformes sont instables (Cloudflare s’améliore)

Pour un contrôle total, retour au VPS.


Stratégie hybride : flexibilité et maîtrise

Beaucoup de projets ne sont ni 100 % statiques ni 100 % backend : Next.js en front, base de données, cron…

L’hybride peut être optimal.

Pages statiques managées + API sur VPS

Architecture type :

  • HTML/CSS/JS sur Cloudflare Pages ou Vercel
  • API Node.js sur votre VPS
  • Base de données sur le VPS (ou Supabase / PlanetScale managé)

Avantages :

  • frontend : CDN et HTTPS automatiques
  • backend : contrôle sans plafonds de la plateforme
  • latence DB faible si API et DB sont sur la même machine

Déploiement multi-étapes dans GitHub Actions

Un seul workflow, deux cibles :

name: Hybrid Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-path: ./dist
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist

  deploy-frontend:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: my-frontend
          directory: dist

  deploy-backend:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup SSH
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
      - name: Deploy API to VPS
        run: |
          rsync -avz --delete \
            --exclude 'node_modules' \
            ./api/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/api/
      - name: Restart API service
        run: |
          ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
            "cd /var/www/api && npm install && pm2 restart api"

Trois jobs :

  1. build : compile et produit les artefacts
  2. deploy-frontend : envoie le statique vers Cloudflare Pages
  3. deploy-backend : déploie l’API sur le VPS et redémarre

needs: build synchronise les déploiements. upload-artifact / download-artifact passent les fichiers entre jobs.

Séparer les variables d’environnement

Front et back n’ont pas les mêmes variables : l’URL d’API côté front, le mot de passe DB côté back.

Exemple :

# Job frontend
- name: Set frontend env
  run: |
    echo "API_URL=https://api.mydomain.com" >> $GITHUB_ENV

# Job backend
- name: Deploy with env
  run: |
    ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
      "cd /var/www/api && pm2 restart api --update-env DATABASE_URL=${{ secrets.DATABASE_URL }}"

Secrets GitHub pour tout ce qui est sensible. L’URL d’API publique peut rester dans le workflow.


Exemple de configuration complète

Workflow VPS couvrant les points ci-dessus.

Fichier workflow complet

name: Deploy to VPS

on:
  push:
    branches: [main]
  workflow_dispatch:  # Déclenchement manuel

env:
  NODE_VERSION: '20'

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

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist
          retention-days: 1

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist

      - name: Setup SSH
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Add server to known hosts
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy files
        run: |
          rsync -avz --delete \
            --exclude '.htaccess' \
            ./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:${{ secrets.DEPLOY_PATH }}

      - name: Verify deployment
        run: |
          ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} \
            "ls -la ${{ secrets.DEPLOY_PATH }}"

      - name: Send deployment notification
        if: always()
        run: |
          curl -X POST "${{ secrets.NOTIFICATION_WEBHOOK }}" \
            -H "Content-Type: application/json" \
            -d '{"text": "Deployment completed: ${GITHUB_SHA}"}'

Secrets à configurer

Nom du secretDescriptionComment l’obtenir
SSH_PRIVATE_KEYContenu de la clé privée SSHGénérée en local, clé publique sur le serveur
SERVER_HOSTIP ou domaine du serveurInfos de votre VPS
SERVER_USERUtilisateur SSHSouvent root ou ubuntu
DEPLOY_PATHChemin de déploiementEx. /var/www/html
NOTIFICATION_WEBHOOKURL de notificationWebhook Slack / Telegram

Dépannage courant

Les logs peuvent submerger en cas d’échec.

Ordre que je suis :

  1. Connexion SSH : étapes « Setup SSH » et « Add server to known hosts »
    • échec → format de clé, contenu de known_hosts
  2. Transfert rsync : étape « Deploy files »
    • échec → chemin existant, permissions
  3. Redémarrage / vérification : étape « Verify deployment »
    • échec → fichiers présents sur le chemin cible

Astuce : ajoutez du debug après l’étape en échec.

- name: Debug SSH connection
  if: failure()
  run: |
    echo "SSH config:"
    cat ~/.ssh/config || echo "No config file"
    echo "Known hosts:"
    cat ~/.ssh/known_hosts || echo "No known_hosts file"
    ssh -v ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} echo "Connection test"

ssh -v affiche le détail utile pour localiser l’erreur.


Synthèse

En bref : pas de déploiement parfait, seulement celui qui colle à votre projet.

Recommandations :

  • Site statique (blog, doc) : Cloudflare Pages ou Vercel, peu de maintenance
  • API simple + frontend : la plateforme managée suffit souvent
  • Backend complexe + base de données : VPS ou serveur cloud pour le contrôle
  • Hybride : frontend managé + backend VPS

Quel que soit le choix, le schéma GitHub Actions reste proche : build → transfert → redémarrage. Trois étapes nettes = debug plus rapide.

En cas d’échec : gardez votre calme, lisez les logs par blocs, SSH ou commandes. Une étape de debug expose souvent la cause tout de suite.

La prochaine fois que ça casse à trois heures du matin, vous saurez où regarder en premier.

Configurer un déploiement VPS avec GitHub Actions

Configurer de bout en bout un déploiement VPS via SSH avec GitHub Actions

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Générer une paire de clés SSH

    Générez sur votre machine une clé dédiée au déploiement :

    • ssh-keygen -t ed25519 -C "deploy@github" -f deploy_key
    • Ajoutez la clé publique (deploy_key.pub) dans ~/.ssh/authorized_keys sur le serveur
    • Stockez le contenu de la clé privée (deploy_key) dans le secret GitHub SSH_PRIVATE_KEY
  2. 2

    Step 2: Configurer les GitHub Secrets

    Dans le dépôt : Settings → Secrets → Actions, ajoutez :

    • SSH_PRIVATE_KEY : contenu complet de la clé privée
    • SERVER_HOST : IP ou nom de domaine du serveur
    • SERVER_USER : utilisateur SSH (ex. root ou ubuntu)
    • DEPLOY_PATH : chemin de déploiement cible
  3. 3

    Step 3: Créer le fichier workflow

    Créez .github/workflows/deploy.yml avec :

    • une étape de configuration SSH (webfactory/ssh-agent-action)
    • known_hosts pour éviter l'échec de vérification d'hôte
    • rsync pour transférer les artefacts de build
    • une commande de redémarrage du service après déploiement
  4. 4

    Step 4: Tester le flux de déploiement

    Poussez du code pour déclencher le déploiement automatique, ou lancez-le à la main :

    • observez les logs de chaque étape
    • en cas d'échec SSH : vérifiez le format de la clé et known_hosts
    • en cas d'échec rsync : vérifiez chemins et permissions
    • ajoutez des étapes de debug si besoin

FAQ

Comment résoudre « Host key verification failed » lors d'un déploiement GitHub Actions ?
Cela arrive quand known_hosts n'est pas configuré lors de la première connexion SSH au serveur. Deux solutions :

• Option 1 : récupérez l'empreinte avec ssh-keyscan et stockez-la dans le secret SSH_KNOWN_HOSTS
• Option 2 : dans le workflow, exécutez ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts

L'option 1 est plus propre et plus sûre.
Où placer la clé SSH ? Peut-on l'écrire directement dans le workflow ?
Absolument pas. La clé privée doit rester dans GitHub Secrets ; le workflow la référence via ${{ secrets.SSH_PRIVATE_KEY }}. Une clé en dur dans le dépôt est visible par tous : risque de sécurité majeur.
Vercel, Cloudflare Pages ou Netlify pour un projet personnel ?
Cloudflare Pages offre le quota gratuit le plus généreux (bande passante illimitée, 500 builds/mois), avec une latence souvent plus stable en Asie. Vercel est idéal pour Next.js, mais les Serverless Functions gratuites sont limitées à 10 s. Netlify est mature, avec un bon traitement des formulaires.

Pour un site purement statique, privilégiez Cloudflare Pages.
Quels avantages à une architecture de déploiement hybride ?
Le frontend sur une plateforme managée profite du CDN et du HTTPS automatique ; le backend sur VPS garde un contrôle total sans limites de plateforme. Adapté aux projets avec base de données, tâches planifiées, etc.

Un workflow multi-jobs GitHub Actions peut déployer les deux en parallèle.
Trop de logs en cas d'échec : comment cibler vite le problème ?
Lisez les logs par étapes, dans l'ordre :

1. Connexion SSH → étapes Setup SSH et known_hosts
2. Transfert rsync → existence du chemin et permissions
3. Redémarrage du service → liste des fichiers sur le chemin cible

Ajoutez du debug après l'étape en échec (ssh -v) pour exposer la cause.
Quels risques avec le paramètre rsync --delete ?
--delete supprime sur la cible les fichiers absents de la source, pour une synchronisation stricte. Un mauvais chemin peut effacer des données importantes.

Lors du premier déploiement, évitez --delete ; activez-le une fois le chemin validé. Sinon, --delete-excluded ne supprime que les fichiers exclus.

9 min de lecture · Publié le: 7 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog