Automatisez le deploiement Angular avec GitHub Actions : workflow YAML, cle SSH dediee, secrets, deploiement atomique avec symlinks, rollback rapide et hardening complet.
Pourquoi automatiser avec GitHub Actions
Deployer manuellement une application Angular se passe bien... jusqu'au jour ou ca deraille. Quelqu'un oublie une etape, fait le `git pull` sur la mauvaise branche, oublie le `npm ci` apres une montee de version, ou execute le build sur sa machine locale et pousse des bundles qui ne correspondent pas au serveur.
Une CI/CD bien faite remplace ces gestes humains par un workflow reproductible. Chaque push sur `main` declenche les memes etapes : tests → build → transfert → reload. Si une etape echoue, le deploiement est stoppe automatiquement. Le serveur reste donc toujours dans un etat coherent, et l'historique de deploiement est tracable directement dans GitHub.
GitHub Actions est l'outil le plus accessible pour cela en 2026 : integration native avec le depot, gratuit jusqu'a 2000 minutes/mois en prive, et syntaxe YAML lisible. Pour une app Angular sur un VPS Linux classique, c'est le bon choix. Cet article montre la configuration exacte pour passer d'un deploiement manuel a une release sur push, en moins d'une heure.
Creer une cle SSH dediee au deploy
Premiere regle de securite : ne reutilisez jamais votre cle SSH personnelle pour la CI. Generez une paire dediee, qui pourra etre revoquee independamment si elle fuit, et qui sera restreinte aux operations de deploiement.
# Sur votre machine locale
ssh-keygen -t ed25519 -C "github-deploy-angular" -f ~/.ssh/deploy_angular_ed25519
# Le fichier ~/.ssh/deploy_angular_ed25519.pub contient la cle publique
cat ~/.ssh/deploy_angular_ed25519.pub
Sur le serveur, creez un utilisateur dedie au deploiement (separe de votre compte sudo) et ajoutez la cle publique :
# Sur le serveur
sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
# Ajout de la cle publique (copier-coller le contenu du .pub)
sudo nano /home/deploy/.ssh/authorized_keys
sudo chmod 600 /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh
Donnez ensuite a `deploy` les droits stricts necessaires sur le repertoire de l'app et l'autorisation de recharger PM2 sans mot de passe :
# Droits sur le repertoire de deploy
sudo mkdir -p /var/www/angular-app
sudo chown deploy:deploy /var/www/angular-app
# Autoriser le reload PM2 sans password (sudoers minimal)
echo "deploy ALL=(angularapp) NOPASSWD: /usr/bin/pm2 reload angular-ssr" \
| sudo tee /etc/sudoers.d/deploy-angular
sudo chmod 440 /etc/sudoers.d/deploy-angular
Testez la connexion avec la nouvelle cle depuis votre poste :
ssh -i ~/.ssh/deploy_angular_ed25519 deploy@votre-serveur.com "echo OK"
# OK
Configurer les GitHub Secrets
Stockez la cle privee et les autres infos sensibles dans Settings > Secrets and variables > Actions du depot GitHub. Ne les ecrivez jamais dans le YAML du workflow.
Secrets minimum a creer :
- `SSH_PRIVATE_KEY` : contenu de `~/.ssh/deploy_angular_ed25519` (cle privee, lignes BEGIN/END comprises)
- `SSH_HOST` : adresse du serveur (ex: `votre-domaine.com` ou IP)
- `SSH_USER` : utilisateur de deploy (ex: `deploy`)
- `SSH_KNOWN_HOSTS` : empreinte du serveur (recuperee via `ssh-keyscan votre-serveur.com`)
Pour generer la valeur de `SSH_KNOWN_HOSTS` :
# Sur votre machine locale
ssh-keyscan -t ed25519 votre-serveur.com
# Output a copier dans GitHub Secrets
# votre-serveur.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
Cette etape est cruciale : sans `known_hosts`, GitHub Actions affiche une erreur "Host key verification failed". Avec, vous evitez egalement les attaques MITM (man-in-the-middle).
Premier workflow : build et tests
Avant le deploiement, creez un premier workflow CI qui valide chaque push : install, lint, tests, build. Si ces etapes echouent, on ne deploie pas.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Tests unitaires
run: npm test -- --watch=false --browsers=ChromeHeadless
- name: Build production
run: npm run build
Pushez ce fichier sur la branche `main`. Dans l'onglet "Actions" du depot, vous voyez immediatement le workflow s'executer. En cas d'erreur, GitHub envoie un email automatique au commiter.
# Test local equivalent (simule la CI)
npm ci
npm run lint
npm test -- --watch=false
npm run build
Pour les pull requests, ajoutez une protection de branche dans Settings > Branches : exigez que ce workflow passe avant tout merge sur `main`.
Workflow de deploiement SSH
Le deuxieme workflow declenche le deploiement reel. Il s'execute uniquement sur les push vers `main` (jamais sur les pull requests) et utilise les secrets SSH definis precedemment.
# .github/workflows/deploy.yml
name: Deploy Production
on:
push:
branches: [main]
workflow_dispatch: # Permet le declenchement manuel
jobs:
deploy:
needs: []
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Deploy via SSH
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
run: |
ssh -i ~/.ssh/id_ed25519 $SSH_USER@$SSH_HOST 'bash -s' < .github/scripts/deploy.sh
- name: Notification Slack
if: always()
run: |
STATUS="${{ job.status }}"
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\":rocket: Deploy ${STATUS} sur production - SHA ${{ github.sha }}\"}" \
${{ secrets.SLACK_WEBHOOK }}
Le script de deploiement reel reste sur le serveur (ou versionne dans `.github/scripts/deploy.sh`) :
#!/usr/bin/env bash
# .github/scripts/deploy.sh
set -euo pipefail
APP_DIR="/var/www/angular-app"
LOG_FILE="/var/log/angular-ssr/deploy.log"
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE"
}
cd "$APP_DIR"
log "=== Debut deploy SHA $(git rev-parse --short HEAD)"
log "Pull du code"
git fetch origin main
git reset --hard origin/main
log "Installation dependances"
npm ci
log "Build production"
npm run build
log "Reload PM2"
sudo -u angularapp pm2 reload angular-ssr
log "=== Deploy termine"
A chaque push sur `main`, GitHub Actions se connecte au serveur et execute ces etapes. Le statut du job remonte dans GitHub avec les logs complets, accessibles meme apres l'execution.
Deploiement atomique avec symlinks
Le script precedent fait un deploiement "in-place" : pendant le `npm ci` + `npm run build`, l'app peut etre dans un etat incoherent. Pour eviter cela, utilisez le pattern Capistrano : chaque deploy creee un nouveau dossier `releases/timestamp`, et un symlink `current` pointe vers la release active.
# Structure cible sur le serveur
# /var/www/angular-app/
# ├── current -> releases/20260506-220000
# ├── releases/
# │ ├── 20260506-220000/
# │ ├── 20260506-180000/
# │ └── 20260505-220000/
# └── shared/
# └── .env
Script de deploy atomique :
#!/usr/bin/env bash
# /home/deploy/deploy-atomic.sh
set -euo pipefail
APP_DIR="/var/www/angular-app"
RELEASES="$APP_DIR/releases"
SHARED="$APP_DIR/shared"
TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
NEW_RELEASE="$RELEASES/$TIMESTAMP"
KEEP_RELEASES=5
mkdir -p "$NEW_RELEASE"
cd "$NEW_RELEASE"
# Recuperation du code
git clone --depth 1 https://github.com/votre-org/mon-app.git .
# Lien vers fichiers partages (env, uploads, etc.)
ln -s "$SHARED/.env" .env
# Build
npm ci --omit=dev=false
npm run build
# Switch atomique du symlink
ln -sfn "$NEW_RELEASE" "$APP_DIR/current"
# Reload PM2 (les workers prennent le nouveau code)
sudo -u angularapp pm2 reload angular-ssr
# Cleanup des anciennes releases
cd "$RELEASES"
ls -t | tail -n +$((KEEP_RELEASES + 1)) | xargs -r rm -rf
echo "Deploy $TIMESTAMP termine"
L'avantage : pendant que le nouveau dossier se prepare, l'ancien continue de servir le trafic. Le switch est instantane (un seul symlink) et n'interrompt aucune requete en cours.
Strategie de rollback automatique
Un deploy peut sembler reussir techniquement mais casser une fonctionnalite cote utilisateur. Avoir une commande de rollback rapide est essentiel : c'est ce qui transforme un incident de 30 minutes en incident de 30 secondes.
#!/usr/bin/env bash
# /home/deploy/rollback.sh
set -euo pipefail
APP_DIR="/var/www/angular-app"
RELEASES="$APP_DIR/releases"
# Trouver la release precedente (la 2e plus recente)
PREVIOUS=$(ls -t "$RELEASES" | sed -n '2p')
if [ -z "$PREVIOUS" ]; then
echo "Aucune release precedente disponible"
exit 1
fi
echo "Rollback vers $PREVIOUS"
ln -sfn "$RELEASES/$PREVIOUS" "$APP_DIR/current"
sudo -u angularapp pm2 reload angular-ssr
echo "Rollback termine"
Pour automatiser le rollback en cas d'echec du healthcheck post-deploy, ajoutez une etape de validation :
# Ajout dans deploy-atomic.sh, apres le pm2 reload
sleep 5
if ! curl -sf https://votre-domaine.com/health > /dev/null; then
echo "Healthcheck KO, rollback automatique"
/home/deploy/rollback.sh
exit 1
fi
echo "Healthcheck OK"
Ce filet de securite garantit qu'une release qui casse le `/health` ne reste pas active plus de quelques secondes.
Bonnes pratiques et hardening
Quelques regles a respecter pour que votre CI/CD reste fiable et secure dans la duree :
- Cle SSH limitee : utilisez `command="..."` dans `authorized_keys` pour restreindre les commandes executables. La cle de deploy ne doit JAMAIS pouvoir lancer `bash -i` ou `rm -rf`.
- Environnement protege : dans GitHub > Environments, ajoutez une protection "Required reviewers" sur l'environnement `production`. Cela force une approbation humaine avant chaque deploy.
- Secrets segregues : stockez les secrets sensibles au niveau Environment (pas Repository), ce qui evite qu'un workflow sur une branche autre que `main` puisse les lire.
- Logs centralises : configurez le workflow pour publier les logs de deploy dans Slack ou Discord. Cela cree un historique parallele a celui de GitHub Actions.
- Tests post-deploy : integrez un smoke test (curl + verif HTTP 200 sur 3-4 routes critiques) apres le `pm2 reload`. Ne considerez pas le deploy reussi tant que ce test ne passe pas.
- Rotation cles : renouvelez la cle SSH de deploy une fois par an minimum. Plus si l'equipe change.
Pour aller plus loin, vous pouvez decouper le workflow en plusieurs jobs avec dependances :
# .github/workflows/deploy.yml (version avancee)
jobs:
test:
# ... lint + tests + build
deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop'
# ... deploy vers staging
deploy-prod:
needs: test
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://votre-domaine.com
# ... deploy vers prod
Conclusion
Mettre en place une CI/CD pour Angular avec GitHub Actions tient en quatre etapes : creer une cle SSH dediee, stocker les secrets dans GitHub, ecrire un workflow CI puis un workflow de deploy SSH. Une fois en place, chaque merge sur `main` declenche automatiquement la sequence test → build → deploy → reload, sans intervention humaine.
Le vrai gain n'est pas seulement technique. Il est organisationnel. L'equipe peut pousser plus souvent, avec moins de stress, parce que chaque release suit le meme parcours et qu'un rollback prend 30 secondes. Les regressions sont plus rares parce que les tests bloquent les deploys avant qu'ils atteignent la prod.
Si vous demarrez aujourd'hui, suivez l'ordre exact de cet article. Validez chaque etape avant de passer a la suivante : SSH manuel d'abord, puis workflow build, puis workflow deploy. C'est cette progression qui evite les erreurs de configuration silencieuses qui ne se voient que lors du premier vrai incident.