This document contains the complete solutions for the 4 missions, the line-by-line explanations of the provided workflows, the repairs for the 3 outages, and the answers to the reflection questions.
Open it only after you have really tried. Reading the solution before you have struggled means missing 80 % of the learning.
config.jsonExample of a valid file:
{
"titre": "Portfolio de <votre nom>",
"sous_titre": "Deploiement automatique via GitHub Actions",
"couleur_fond": "#7c3aed",
"couleur_texte": "#f8fafc",
"couleur_accent": "#22d3ee",
"auteur": "<Votre Nom>",
"version": "1.0.0"
}Points to respect:
titre, sous_titre, couleur_fond, couleur_texte, couleur_accent, auteur, version) are required.#RRGGBB format (6 hexadecimal characters after the #).Local validation:
python .\outils\build.pyMust print OK dist/index.html genere and OK dist/css/style.css genere. Any ERREUR : stops the script and will then block the GitHub workflow.
Suggested palette to test the loop in mission 2:
| Color | Background | Text | Accent |
|---|---|---|---|
| Dark night | #0f172a | #f1f5f9 | #38bdf8 |
| Vibrant purple | #7c3aed | #f8fafc | #22d3ee |
| Bright red | #dc2626 | #fef2f2 | #facc15 |
| Forest green | #16a34a | #f0fdf4 | #fbbf24 |
| Ocean blue | #0891b2 | #ecfeff | #f472b6 |
deployer-pages.ymlname: Deployer sur GitHub Pages # name shown in the Actions UIThis name is purely cosmetic — it helps you spot the workflow in the repository Actions tab.
on:
push:
branches: [main]
workflow_dispatch:Two triggers:
main (the normal case).permissions:
contents: read
pages: write
id-token: writeThe magic trio for Pages. Without these three lines, you get Resource not accessible by integration on the deployer job (that is exactly outage 1 of mission 3):
contents: read — lets actions/checkout@v4 read the code.pages: write — authorizes actions/deploy-pages@v4 to publish.id-token: write — required for OIDC authentication between the runner and the Pages service.concurrency:
group: pages
cancel-in-progress: falseIf two git push arrive almost at the same time, GitHub Actions does not launch two deployments in parallel on the same pages group — the runs execute one after the other. cancel-in-progress: false = let the current run finish before starting the next one. This is critical for Pages: two simultaneous publications produce inconsistent states.
jobs:
construire:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # 1. fetch the code
- uses: actions/setup-python@v5 # 2. install Python
with:
python-version: "3.12"
- run: python outils/build.py # 3. generate dist/
- uses: actions/upload-pages-artifact@v3 # 4. prepare the artifact
with:
path: dist # <-- IMPORTANT: folder to publishFour sequential steps. If one fails, the following ones do not run.
deployer:
needs: construire # waits until "construire" succeeds
environment:
name: github-pages # official Pages environment
url: ${{ steps.publication.outputs.page_url }} # displays the URL in the UI
steps:
- uses: actions/deploy-pages@v4
id: publicationThe deployer job explicitly waits for construire thanks to needs:. At the end, the GitHub UI shows a clickable link to https://<vous>.github.io/<depot>/.
verifier-config.ymlon:
pull_request:
branches: [main]
push:
branches-ignore: [main]It triggers on PRs targeting main and on pushes to every branch except main. The goal: validate before the code is merged. Note that we exclude main from push: — otherwise we would duplicate the work of deployer-pages.yml.
The rest of the steps is identical to deployer-pages.yml except that we do not publish: we only check that dist/ is generated without error.
Sequence to reproduce twice:
# --- Iteration 1: purple background ---
# Edit site/config.json : "couleur_fond": "#7c3aed"
git add site/config.json
git commit -m "fond violet"
git push
# Wait ~40 s -> open https://<vous>.github.io/<depot>/ -> purple background
# --- Iteration 2: red background ---
# Edit site/config.json : "couleur_fond": "#dc2626"
git add site/config.json
git commit -m "fond rouge"
git push
# Wait ~40 s -> open the same URL -> red backgroundChecks to include in the report:
git log --oneline: shows the 2 commits with their short SHAs.Deployer sur GitHub Pages are green.Diff of the fix in .github/workflows/casse-1-permissions-manquantes.yml:
name: Casse 1 - Permissions manquantes
on:
push:
branches: [main]
workflow_dispatch:
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
concurrency:
group: pages
cancel-in-progress: false
jobs:
...Written diagnosis to put in the report:
Without the permissions: block, GitHub grants the workflow only contents: read (read the code) — not the permission to publish on Pages. The actions/deploy-pages@v4 action explicitly requires pages: write and id-token: write (OIDC token). Hence the error Resource not accessible by integration (403 Forbidden) on the deployer job.
Security rule: give the fewest permissions possible, but not fewer. The least privilege principle applied to CI/CD.
Diff of the fix:
name: Casse 2 - Declencheur errone
on:
- pushh:
+ push:
branches: [main]Written diagnosis to put in the report:
GitHub Actions does not report unknown triggers in on: — it ignores them silently. A simple pushh (with two h) means no run is triggered: you see no error, you see nothing at all in the Actions tab.
How do you discover the outage? You push a commit, you go to Actions, you notice that no run appears for this workflow. The only clue is the absence.
Anti-trap tip: a quick Run workflow (manual button, available if workflow_dispatch: is present) checks that the workflow itself is syntactically valid. workflow_dispatch: works independently of push:.
Diff of the fix:
- name: Preparer le dossier a publier
uses: actions/upload-pages-artifact@v3
with:
- path: public
+ path: distWritten diagnosis to put in the report:
outils/build.py always writes into dist/ (constant DIST = RACINE / "dist" in the Python code). The workflow asked for path: public — a folder that does not exist. Hence:
Error: Path does not exist: ./publicactions/upload-pages-artifact@v3 checks that the folder exists before creating the artifact and fails immediately if the path is wrong. Best practice: always point the workflow at the exact folder produced by the build script.
README.mdIn the README.md of your GitHub repository (at the root, not in the course folder projet14-github-actions-pages-tp/), add:
# Mon site portfolio

Site publie : https://<vous>.github.io/<depot>/The badge turns green if the latest run passed, red otherwise. It updates by itself.
config.jsongit checkout -b test-config-cassee
# Edit site/config.json and replace "couleur_fond": "#0f172a" with "couleur_fond": "rouge"
git add site/config.json
git commit -m "test : couleur invalide"
git push -u origin test-config-cassee
# Then on GitHub: open a PR from test-config-cassee to mainThe Verifier la configuration workflow will trigger, build.py will shout:
ERREUR : couleur_fond doit etre au format #RRGGBB (recu : 'rouge')The PR shows a red square and the Merge pull request button is blocked if you configured branch protection (Settings → Branches → Add rule → Require status checks to pass).
.github/workflows/notifier.yml:
name: Notifier une URL
on:
workflow_dispatch:
jobs:
notifier:
runs-on: ubuntu-latest
steps:
- name: Ping du webhook
env:
URL: ${{ secrets.URL_WEBHOOK }}
run: |
curl -X POST "$URL" \
-H "Content-Type: application/json" \
-d '{"texte":"Un utilisateur a declenche notifier.yml"}'The secret URL_WEBHOOK is defined in Settings → Secrets and variables → Actions → New repository secret. It will never appear in the logs: GitHub automatically masks any value that matches a secret.
1. on: push without branches: — when does it trigger?
It triggers on every push to every branch. That is rarely desirable: a git push on a feature branch still in development would launch a Pages deployment in production. Always restrict with branches: [main] or an explicit list.
2. Why is id-token: write required for Pages? What is the OIDC token for?
actions/deploy-pages@v4 uses OIDC (OpenID Connect) to prove to the Pages service that the request really comes from an authorized GitHub Actions run, without using a password or a PAT (Personal Access Token). The OIDC token is ephemeral (lifetime limited to the run) and signed by GitHub — safer than a static secret. id-token: write authorizes the runner to generate that token for this workflow.
3. Two simultaneous pushes on main — what does concurrency do?
Without concurrency, GitHub launches two workflows in parallel. The first finishes its artifact upload, the second starts its own upload, and Pages can receive both publications out of order → the final order is no longer guaranteed. With concurrency: { group: pages, cancel-in-progress: false }, the two runs are serialized: the second waits until the first finishes. cancel-in-progress: true would have another effect — cancel the current run as soon as a new one starts, useful for an ultra-fast rebuild but dangerous for Pages.
4. Can a secret appear in the logs?
GitHub automatically masks any value registered as a secret: it appears as *** in the logs. But if you transform the secret (for example echo "$SECRET" | base64), the masking no longer applies to the transformed value. Rule: never transform a secret in a run:, use it as-is via env: and pass it directly to the tool (here curl).
5. Why keep both workflows deployer-pages.yml and verifier-config.yml?
deployer-pages.yml publishes on main: it is the delivery workflow. It runs only after a merge.verifier-config.yml validates on feature branches and on PRs: it is the prevention workflow. It stops a broken config.json from reaching main.Together they form a two-layer safety net: verification blocks upstream, deployment delivers downstream. That is the CI (Continuous Integration) + CD (Continuous Deployment) pattern.
6. What would happen if build.py created a historique.log file?
Nothing durable. The runner is an ephemeral VM destroyed at the end of the run. The file would disappear with the VM. To keep a history between runs, you would need to commit it in the repository (meh, pollutes the repo), store it in an artifact with actions/upload-artifact (90-day max lifetime), or send it to an external service (S3, Postgres, and so on).
| Symptom | Likely cause | Solution |
|---|---|---|
Resource not accessible by integration | Missing or incomplete permissions: block | Add pages: write + id-token: write |
| No run is triggered | Typo in on: (pushh:, pull_requests:) | Check the spelling of the YAML keys |
Path does not exist: ./xxx | Path in upload-pages-artifact.path: does not match the build.py output folder | Check that path: = dist |
| Published page shows the old content | Browser cache | Ctrl+Shift+R for a hard reload |
| The first deployment takes > 5 min | GitHub Pages DNS propagation | Normal the first time, ~40 s afterwards |
Red workflow at the python outils/build.py step | Invalid config.json | Run python .\outils\build.py locally to see the exact error |
| The workflow runs but the URL returns 404 | Pages source still in “Deploy from a branch” mode | Switch to GitHub Actions in Settings → Pages |
| Two Pages runs at once produce inconsistent content | Missing concurrency: | Add the concurrency: { group: pages } block |
The instructor opens three tabs:
config.json, presence of .github/workflows/ with at least the 3 repaired workflows (the originals deployer-pages.yml and verifier-config.yml + the 3 repaired outages).Deployer sur GitHub Pages (mission 2), plus the mission 3 runs (before/after repair).https://<vous>.github.io/<votre-depot>/ — must display with your customized colors and a recent SHA.Then they read your RAPPORT.md:
A clean report + a well-structured repository = an easy full score. The difficulty is not technical, it is in the rigor of the demonstration.
Course created by Dr. Haythem REHOUMA — Development and Deployment of Data Solutions