
Un lab clé en main de 40 minutes pour démystifier GitHub Actions et Azure Container Instances.
Introduction
J’ai préparé ce lab pour un Knowledge Sharing interne chez Devoteam. L’objectif : montrer concrètement comment fonctionne le déploiement automatisé avec des containers, de A à Z, en partant de zéro avec un site imaginaire simpliste. Pas de théorie interminable, juste du concret.
On crée un site, on le containerise, on pousse sur GitHub, et ça se déploie tout seul sur Azure. Le tout en 40 minutes. Je partage ici le déroulé complet pour ceux qui voudraient le reproduire ou l’adapter. ⬇️
Si vous avez envie de voir l’ensemble du code de test immédiatement, je vous invite à consulter le repository GitHub qui est disponible à l’adresse suivante.
Pourquoi ce lab ?
Le constat
Beaucoup de développeurs et administrateurs système n’ont jamais touché aux containers. Ils en entendent parler, voient passer des mots comme Docker, Kubernetes, CI/CD, mais n’ont jamais franchi le pas. La raison ? Une impression de complexité et un manque de cas d’usage concret pour démarrer.
L’objectif de la démo
Au travers un exemple ultra basique, nous allons dérouler un exemple concret et visuel que vous pourrez reproduire de votre côté.
À la fin du lab, l’idée c’est de pouvoir faire les choses suivantes :
- Créé un site web simple (moi je pars sur quelque-chose de visuel et basique mais votre site pourrait tout à fait être composé de centaines de pages)
- Containerisé l’application avec Docker
- Mis en place un pipeline CI/CD avec GitHub Actions
- Déployé automatiquement sur Azure Container Instances
Les 2 briques Azures que nous allons utiliser :
- ACR (Azure Container Registry) : ton Docker Hub privé dans Azure, il stocke les images de nos containers.
- ACI (Azure Container Instances) : le serveur qui exécute ton container
Les prérequis
Avant de commencer, assurez-vous d’avoir :
- Docker Desktop installé et fonctionnel
- Un compte GitHub
- Un abonnement Azure (un abonnement d’essai suffit)
- Azure CLI
Étape 1 : Créer un site web simple
On part sur une structure classique que tout le monde connaît : HTML, CSS et JavaScript séparés. Pas de framework, pas de complexité. L’objectif est que les participants se concentrent sur le déploiement, pas sur le code. Tout en ayant quelque-chose de visuel à observer.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Demo GitHub Actions - Devoteam</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<img src="https://upload.wikimedia.org/wikipedia/commons/7/79/Dev_logo_rgb.png" alt="Devoteam" class="logo">
<h1>Hello Devoteam!</h1>
<p>Déploiement automatique via GitHub Actions</p>
<div class="version">Version: 1.0.0</div>
<div class="tech-stack">Node.js + Docker + Azure Container Instances</div>
</div>
</body>
</html>
style.css
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #f8485e 0%, #3c3c3a 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
.logo {
width: 200px;
margin-bottom: 2rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
p {
font-size: 1.2rem;
opacity: 0.9;
color: #fca2ae;
}
.version {
margin-top: 2rem;
padding: 1rem 2rem;
background: rgba(255,255,255,0.15);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.2);
}
.tech-stack {
margin-top: 1.5rem;
font-size: 0.9rem;
color: #d7ebe7;
}
server.js
Le fichier JavaScript qui sert nos fichiers statiques :
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
// Déterminer le fichier à servir
let filePath = req.url === '/' ? 'index.html' : req.url.substring(1);
// Déterminer le type de contenu
const ext = path.extname(filePath);
const contentTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript'
};
const contentType = contentTypes[ext] || 'text/plain';
// Lire et servir le fichier
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(200, { 'Content-Type': contentType + '; charset=utf-8' });
res.end(content);
}
});
});
const port = process.env.PORT || 3000;
server.listen(port, () => {
console.log('Server running on port ' + port);
});
package.json
{
"name": "devo-tribe-azure-ks-dec-2025",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
}
}
Evidemment, n’hésitez pas à renommer les fichiers selon vos envies ou à changer le code HTML. J’ai séparé l’ensemble du code en plusieurs fichiers distincts afin de retrouver la logique standard du développement web à savoir un fichier CSS, HTML et JavaScript.
A ce moment-là vous pouvez déjà vérifier dans vos navigateur le look de notre page de test – mais nous allons plus loin afin de pouvoir mettre note site en container. ⬇️
Étape 2 : Containeriser avec Docker
Nous allons ajouter un nouveau fichier qui va nous permettre de créer un container. Pour que ça marche, vous devez avoir installé et démarré Docker Desktop sur votre ordinateur.
Le Dockerfile expliqué
C’est ce fichier qui va nous permettre de créer un container de notre site.
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY server.js ./
COPY index.html ./
COPY style.css ./
EXPOSE 3000
CMD ["npm", "start"]
Ligne par ligne, voici ce que cela dit :
FROM node:20-alpine: Part d’une image Linux légère avec Node.js préinstalléWORKDIR /app: Crée et se place dans le dossier /appCOPY package.json: Copie la config des dépendancesRUN npm install: Installe les dépendancesCOPY ...: Copie nos fichiersEXPOSE 3000: Documente le port utilisé (sur lequel notre site sera visible)CMD: Commande lancée au démarrage
Build et test local
Exécutez les commandes suivantes :
docker build -t devo-tribe-azure-ks-dec-2025 .
docker run -d -p 3000:3000 --name devo-tribe-azure-ks-dec-2025 devo-tribe-azure-ks-dec-2025
Vous devriez avoir le rendu suivant dans votre terminal :
Et dans Docker Desktop, vous verrez que vous avez 1 image et 1 container en cours d’exécution basé sur cette image.
Par défaut, notre site sera accessible sur le port 3000. Donc ouvrez http://localhost:3000 pour votre vote site et vérifiez que ça fonctionne bien.

Point important : je n’ai même pas Node.js installé sur ma machine. Tout est dans le container. C’est justement ça tout l’intérêt de Docker dans notre cas d’usage. 👍
Voici ce que j’ai dans mon VS Code :

A ce niveau, vous pouvez donc modifier votre site internet, ajouter des pages, transformer le code, créer une nouvelle version et valider localement sur votre ordinateur que le rendu est conforme à vos attentes. Quand vous êtes prêt, n’oubliez pas commit et push votre code au sein de votre repos GitHub (nous en aurons besoin pour utiliser GitHub Actions).
Étape 3 : Préparer Azure
Créer les ressources
Connectez-vous en Azure CLI à votre Tenant et souscription. Commençons par créer une Resource Group de test et notre objet Azure Container Registry.
$RG_NAME="RG-Demo-CICD"
$ACR_NAME="acrdevoteamdemo"
az acr create --resource-group $RG_NAME --name $ACR_NAME --sku Basic
Note : le nom de l’ACR doit être unique et en minuscules.
Créer le Service Principal
Le Service Principal permet à GitHub de s’authentifier à Azure (nous en aurons besoin afin de permettre à GitHub Actions de gérer notre code et mettre à jour notre code tout seul) :
$RG_NAME="RG-Demo-CICD"
$SUBSCRIPTION_ID="votre-subscription-id"
az ad sp create-for-rbac --name "sp-github-actions-demo" --role contributor --scopes /subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RG_NAME
Cette commande retourne un JSON avec 4 valeurs importantes :
appId: AZURE_CLIENT_IDpassword: AZURE_CLIENT_SECRETtenant: AZURE_TENANT_ID- Et votre AZURE_SUBSCRIPTION_ID
Gardez les de côté car nous allons créer des GitHub Secrets pour chacune de ces infos. Si vous le souhaitez, vous pouvez également aller vérifier dans Entra ID et vous verrez que vous disposez d’une nouvelle App Registration correspondant au nom de notre Service Principal : sp-github-actions-demo.
Configurer les secrets GitHub
Dans votre repo GitHub : Settings → Secrets and variables → Actions → New repository secret
Créez ces 4 secrets :
AZURE_CLIENT_IDAZURE_CLIENT_SECRETAZURE_TENANT_IDAZURE_SUBSCRIPTION_ID
Étape 4 : Le workflow GitHub Actions
Toujours dans votre dossier de travail, nous allons générer un dossier ainsi qu’un fichier avec l’extension YML à un emplacement bien particulier. Ne vous trompez pas car c’est ce qui permet à GitHub de savoir qu’il va devoir réaliser des tâches seul (ce que l’on appelle un workflow).
Dans notre scénario de test, je vous rappelle que l’objectif c’est que dès que nous réalisons un commit + push de notre code et bien cela soit mis à jour tout seul dans Azure ! ♾️
Créez le fichier .github/workflows/deploy.yml :
name: Build and Deploy to Azure
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
with:
creds: |
{
"clientId": "${{ secrets.AZURE_CLIENT_ID }}",
"clientSecret": "${{ secrets.AZURE_CLIENT_SECRET }}",
"subscriptionId": "${{ secrets.AZURE_SUBSCRIPTION_ID }}",
"tenantId": "${{ secrets.AZURE_TENANT_ID }}"
}
- name: Login to Azure Container Registry
run: |
az acr login --name acrdevoteamdemo
- name: Build and push image to ACR
run: |
az acr build \
--registry acrdevoteamdemo \
--image devo-tribe-azure-ks-dec-2025:${{ github.sha }} \
--image devo-tribe-azure-ks-dec-2025:latest \
--file Dockerfile \
.
- name: Deploy to Azure Container Instances
run: |
az container create \
--resource-group RG-Demo-CICD \
--name devo-tribe-azure-ks-dec-2025 \
--image acrdevoteamdemo.azurecr.io/devo-tribe-azure-ks-dec-2025:latest \
--registry-login-server acrdevoteamdemo.azurecr.io \
--registry-username ${{ secrets.AZURE_CLIENT_ID }} \
--registry-password ${{ secrets.AZURE_CLIENT_SECRET }} \
--dns-name-label devo-tribe-azure-ks-dec-2025 \
--ports 80 \
--os-type Linux \
--cpu 1 \
--memory 1 \
--environment-variables PORT=80
Ce que fait ce workflow ↙️
➡️ Le bloc on définit les déclencheurs : le workflow s’exécute à chaque push sur la branche main, ou manuellement via l’interface GitHub (c’est le workflow_dispatch).
➡️ Les variables d’environnement (env) centralisent la configuration : nom du registry, de l’image, du resource group, etc. Ça évite de répéter ces valeurs partout et facilite l’adaptation à d’autres projets.
➡️ Le job build-and-deploy tourne sur une machine Ubuntu hébergée par GitHub. Les steps s’enchaînent :
- Checkout : récupère le code du repo
- Azure login : s’authentifie à Azure avec le Service Principal (les credentials sont stockés dans les secrets GitHub)
- Login ACR : se connecte au Container Registry pour pouvoir y pousser notre image
- Build and push : construit l’image Docker directement dans Azure (via
az acr build) et la stocke dans l’ACR - Deploy to ACI : crée ou met à jour le container dans Azure Container Instances avec l’image qu’on vient de builder
Le container est configuré avec 1 CPU, 1 Go de RAM, et expose le port 80. La variable d’environnement PORT=80 est passée au container pour que notre serveur Node.js écoute sur le bon port.
Tout est prêt – il ne nous reste plus qu’à tester ! 🤯
Étape 5 : La magie du déploiement automatique
Premier déploiement
Une fois que vous êtes prêt, il ne vous reste plus qu’à commit & push votre code sur GitHub. Si votre workflow est bon, cela aura pour effet d’exécuter notre workflow GitHub Actions qui doit créer notre Azure Container Instance et publier notre code. Notre site devrait alors être accessible via une URL générée par Azure. 😉
Que vous fassiez votre push & commit via VS Code ou en CLI, vous pourrait observer l’effet dans GitHub Actions :
Notre workflow s’est correctement exécuté. Aucune erreur. ✅
Nous pouvons maintenant aller dans notre Resource Group de test et voir si notre ACI a été créé :
Et retrouver l’URL d’accès : devo-tribe-azure-ks-dec-2025.francecentral.azurecontainer.io dans mon cas.
Attention lors du test, notre site écoute sur le port 80 et les nouveaux navigateurs d’aujourd’hui ont tendance à forcer le https sur le port 443/TCP. Donc forcez bien http sans le S dans l’URL. 😉
Bravo, notre site est fonctionnel ! ✅
Mise à jour et publication automatique
Maintenant ce qui est intéressant : imaginez que vous avez des mises à jour à effectuer sur votre site. Dans mon exemple, je vais juste changer dans index.html le Version 1.0.0 en 2.1.0 par exemple (ou même un couleur dans le CSS).
Il vous suffit de faire vos modifications et quand vous avez terminé, sauvegardez et faîtes un commit + push de votre code sur GitHub. Cela aura pour effet automatique de déclencher notre workflow et mettre à jour notre site. 😎
git add .
git commit -m "Update to version 2.0.0"
git push
Rafraîchissez la page Azure après 2 minutes : la version a changé automatiquement. Pas de FTP, pas de copier-coller, pas d’intervention manuelle. Votre site est à jour. 👍
Nous pouvons voir que notre site a bien été mis à jour !
Conclusion
Ce que nous avons compris :
- Les containers isolent l’application : « ça marche sur ma machine » devient « ça marche partout »
- Le CI/CD automatise le déploiement : push = déploiement, pas besoin d’intervention
- C’est reproductible : le workflow est versionné dans Git, tout le monde peut le relancer
- C’est accessible : pas besoin d’être expert DevOps pour commencer
Pour aller plus loin
Ce lab utilise Azure Container Instances (ACI) qui est parfait pour une démo ou du dev. Pour la production, explorez :
- Azure Container Apps : scaling automatique, plus de résilience
- Azure Kubernetes Service (AKS) : pour les architectures microservices complexes
- Azure App Service : si vous n’avez pas besoin de containers
Cet article est basé sur une présentation / démo interne réalisée au sein de Devoteam M Cloud en décembre 2025. Vous pouvez retrouver le repository Github avec le code en suivant ce lien. Ainsi qu’une copie de la présentation PowerPoint ci-dessous.














