Solution — Project 14: GitHub Actions + GitHub Pages

10 min

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.


Table of contents


Mission 1 — Customize config.json

Example of a valid file:

json
{
  "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:

  1. The 7 keys (titre, sous_titre, couleur_fond, couleur_texte, couleur_accent, auteur, version) are required.
  2. The 3 colors must follow the #RRGGBB format (6 hexadecimal characters after the #).
  3. The file must remain valid JSON: a comma after each field except the last, double quotes everywhere.

Local validation:

powershell
python .\outils\build.py

Must 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:

ColorBackgroundTextAccent
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

Mission 2 — Understand the provided workflows + prove the push → Pages loop

Line-by-line reading of deployer-pages.yml

yaml
name: Deployer sur GitHub Pages          # name shown in the Actions UI

This name is purely cosmetic — it helps you spot the workflow in the repository Actions tab.

yaml
on:
  push:
    branches: [main]
  workflow_dispatch:

Two triggers:

  • Automatic as soon as you push to main (the normal case).
  • Manual via a Run workflow button in the Actions tab (useful to redeploy without a new commit).
yaml
permissions:
  contents: read
  pages: write
  id-token: write

The 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.
yaml
concurrency:
  group: pages
  cancel-in-progress: false

If 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.

yaml
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 publish

Four sequential steps. If one fails, the following ones do not run.

yaml
  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: publication

The 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>/.

Line-by-line reading of verifier-config.yml

yaml
on:
  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.

Proof of the push → Pages loop

Sequence to reproduce twice:

powershell
# --- 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 background

Checks to include in the report:

  • git log --oneline: shows the 2 commits with their short SHAs.
  • Actions tab: the 2 runs of Deployer sur GitHub Pages are green.
  • Two screenshots of the public page with the 2 different colors.
  • On each screenshot, the “Commit SHA” tile must match the commit that triggered that run.

Mission 3 — Repair of the 3 outages

Outage 1 — Missing permissions

Diff of the fix in .github/workflows/casse-1-permissions-manquantes.yml:

diff
 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.

Outage 2 — Wrong trigger

Diff of the fix:

diff
 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:.

Outage 3 — Wrong artifact path

Diff of the fix:

diff
       - name: Preparer le dossier a publier
         uses: actions/upload-pages-artifact@v3
         with:
-          path: public
+          path: dist

Written 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: ./public

actions/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.


Mission 4 — Bonus

4.a — Status badge in README.md

In the README.md of your GitHub repository (at the root, not in the course folder projet14-github-actions-pages-tp/), add:

markdown
# Mon site portfolio

![Deploy](https://github.com/<vous>/<depot>/actions/workflows/deployer-pages.yml/badge.svg)

Site publie : https://<vous>.github.io/<depot>/

The badge turns green if the latest run passed, red otherwise. It updates by itself.

4.b — Test PR with an invalid config.json

powershell
git 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 main

The 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).

4.c — Workflow with a secret

.github/workflows/notifier.yml:

yaml
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.


Answers to the reflection questions

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).


Classic mistakes to avoid

SymptomLikely causeSolution
Resource not accessible by integrationMissing or incomplete permissions: blockAdd pages: write + id-token: write
No run is triggeredTypo in on: (pushh:, pull_requests:)Check the spelling of the YAML keys
Path does not exist: ./xxxPath in upload-pages-artifact.path: does not match the build.py output folderCheck that path: = dist
Published page shows the old contentBrowser cacheCtrl+Shift+R for a hard reload
The first deployment takes > 5 minGitHub Pages DNS propagationNormal the first time, ~40 s afterwards
Red workflow at the python outils/build.py stepInvalid config.jsonRun python .\outils\build.py locally to see the exact error
The workflow runs but the URL returns 404Pages source still in “Deploy from a branch” modeSwitch to GitHub Actions in Settings → Pages
Two Pages runs at once produce inconsistent contentMissing concurrency:Add the concurrency: { group: pages } block

How the instructor evaluates your work

The instructor opens three tabs:

  1. Your GitHub repository: file structure, content of 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).
  2. Your Actions tab: at least 2 green runs of Deployer sur GitHub Pages (mission 2), plus the mission 3 runs (before/after repair).
  3. Your published site: https://<vous>.github.io/<votre-depot>/ — must display with your customized colors and a recent SHA.

Then they read your RAPPORT.md:

  • They count the screenshots (2 for mission 2, 6 for mission 3: before/after × 3 outages).
  • They check that the outage diagnoses are written in your own words, not copied from this document.
  • They grade the quality of the answers to the 6 reflection questions.

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