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

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 :
- Environnement de build contraint : RAM et CPU plafonnés ; gros projets peuvent échouer au build
- Peu de personnalisation : modifier nginx ? Impossible
- Dépendance à la plateforme : politique ou fermeture → migration
- 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 :
build: compile et produit les artefactsdeploy-frontend: envoie le statique vers Cloudflare Pagesdeploy-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 secret | Description | Comment l’obtenir |
|---|---|---|
SSH_PRIVATE_KEY | Contenu de la clé privée SSH | Générée en local, clé publique sur le serveur |
SERVER_HOST | IP ou domaine du serveur | Infos de votre VPS |
SERVER_USER | Utilisateur SSH | Souvent root ou ubuntu |
DEPLOY_PATH | Chemin de déploiement | Ex. /var/www/html |
NOTIFICATION_WEBHOOK | URL de notification | Webhook Slack / Telegram |
Dépannage courant
Les logs peuvent submerger en cas d’échec.
Ordre que je suis :
- Connexion SSH : étapes « Setup SSH » et « Add server to known hosts »
- échec → format de clé, contenu de known_hosts
- Transfert rsync : étape « Deploy files »
- échec → chemin existant, permissions
- 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
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
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
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
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 ?
• 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 ?
Vercel, Cloudflare Pages ou Netlify pour un projet personnel ?
Pour un site purement statique, privilégiez Cloudflare Pages.
Quels avantages à une architecture de déploiement hybride ?
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 ?
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 ?
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
Guide complet GitHub Actions
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Matrix GitHub Actions : guide pratique des tests parallèles multi-plateformes et multi-versions
Guide pratique du Matrix GitHub Actions : de la syntaxe de base à exclude/include, fail-fast et max-parallel, avec 5 modèles de workflow prêts à l'emploi pour générer des tests parallèles multi-plateformes et réduire de 60 %+ le code de configuration.
Partie 5 sur 10
Suivant
Secrets GitHub Actions : du risque de fuite au déploiement OIDC sans clé
Guide de gestion des Secrets GitHub Actions : stratégie à trois niveaux, 8 règles de sécurité, déploiement OIDC sans clé, protection contre les attaques supply chain. Leçons de l'incident tj-actions, avec exemples YAML et bonnes pratiques.
Partie 7 sur 10



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire