Site Logo
MBS - Yanis MERCIER

Implémentation de mon blog - MBS

Posted on 6 mins

Cicd Hugo Cloud Workflow

Pourquoi un blog ?

https://mbs.yanismercier.fr/about/#pourquoi-ce-blog-

Ce blog va me permettre de mettre en note certaines expériences que je fait sur mon temps libre. Ceci me servira de prise de note “RTM In Case I Forget” (“Lis le manuel au cas où j’oublie”) et au cas où des personnes veulent lire : Si mes notes peuvent être utiles à d’autres, c’est encore mieux.

En plus, cela me permet d’avoir une alternative plus intéressante à un simple CV tout en m’efforçant à continuer à faire de la veille technique sur mon temps libre (finir les études peut faire un certain choque technique).

Mon Workflow

Pour aider à comprendre mon choix au niveau de ces technologies, il est important de comprendre mon flux de travail dessus. Même si à cette heure, il n’est que très peu établie !

flowchart TD A[Obsidian] -->|Écriture: OK| B[VSCode] B -->|git push| C[Gitlab] C --> G[GitlabCI] G -->|Tâches de build/test| H[Dépôt d'artefacts] G -->|Tâches de déploiement| E[NGINX de prod] E -->|Conteneur Docker| I[Mise à jour en direct]

1. Écriture et formatage

J’écris et je formate mon texte avec Obsidian , un gestionnaire complet de note en markdown. Une fois que je suis OK avec ce que j’ai écris, j’ouvre mon IDE Visual Studio Code et je créé mon article avec le CLI de hugo (qui est totalement optionnel d’ailleurs !):

hugo new content posts/new-article.md

Cette commande permet de générer l’entête du fichier markdown.

---
title: 'À propos de moi'
date: 2025-08-19T13:21:26+02:00
draft: false
hidden: false
externalURL: false
showDate: false
showModDate: false
showReadingTime: false
showTags: false
showPagination: false
invertPagination: false
showToC: false
openToC: false
showComments: false
showHeadingAnchors: true
categories:
---

Je créé un dossier du même nom : new-article. Et je met l’entête généré dans un fichier nommé index.md à la racine de new-article. Ce méthode permet d’avoir un vrai dossier de travaille pour chaque article !


Pour mes tests en local j’ai un fichier docker-compose à la racine de mon projet qui a deux fonctions:

services:
  build:
    image: hugomods/hugo:dart-sass
    command: build
    volumes:
      - "./src:/src"
  serve:
    image: hugomods/hugo:dart-sass
    command: server -D
    volumes:
      - "./src:/src"
    ports:
      - 8080:1313
docker compose up build
docker compose up serve

2. Synchronisation et CI/CD

Mon projet est en parallèle sur mon Gitlab personnel. En plus de gérer mon versionning sur le Cloud, Gitlab me permet de mettre en place des pipeline CI/CD (Intégration Continue/Déploiement Continue).

Voici comment je me sert de git.

--- title: MBS - Mandadory Blog Site Git Graph --- gitGraph commit branch draft checkout draft commit commit checkout main merge draft commit checkout draft branch ci checkout ci commit commit checkout draft merge ci commit checkout main merge draft

Quand je synchronise mon projet sur Gitlab mes pipelines sont exécutées dépendamment de la branche que je pousse. Voici mon .gitlab-ci.yml

default:
  image: alpine:latest

stages:
  - build
  - deploy

variables:
  HUGO_BASE_URL_DRAFT: "https://mbs-draft.yanismercier.fr"
  HUGO_BASE_URL_PROD: "https://mbs.yanismercier.fr"

.deploy_template:
  variables:
    SECURE_FILES_DOWNLOAD_PATH: './'
  before_script:
    - apk add --no-cache openssh-client rsync curl bash
    - mkdir -p /root/.ssh
    - cd /root/.ssh && curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash && cd /builds/yanis.mercier51/mbs/
    - mv /root/.ssh/deploy_key /root/.ssh/id_rsa
    - chmod 600 /root/.ssh/id_rsa
    - ssh-keyscan -H 5.135.151.51 >> /root/.ssh/known_hosts

build-site:
  stage: build
  image: 
    name: ghcr.io/hugomods/hugo:base
  script:
    - cd src
    - |
      if [ "$CI_COMMIT_REF_NAME" == "draft" ]; then
        echo "Building draft site..."
        hugo --minify --baseURL $HUGO_BASE_URL_DRAFT -D -d public/draft
      else
        echo "Building production site..."
        hugo --minify --baseURL $HUGO_BASE_URL_PROD -d public/prod
      fi
  artifacts:
    paths:
      - src/public/
    expire_in: 1 week

deploy-draft:
  stage: deploy
  extends: .deploy_template
  script:
    - rsync -avz --delete ./src/public/draft/ debian@5.135.151.51:/home/debian/mbs/draft/
  only:
    - draft

deploy-prod:
  stage: deploy
  extends: .deploy_template
  script:
    - rsync -avz --delete ./src/public/prod/ debian@5.135.151.51:/home/debian/mbs/prod/
  only:
    - main

Voici un diagramme expliquant le CI/CD:

--- config: layout: dagre --- graph TD A[Début du pipeline] --> B{Commit sur une branche}; B -->|Branche 'draft'| C[Exécution 'build-site']; B -->|Branche 'main'| C[Exécution 'build-site']; C --> D{Choix du build}; D -->|Si 'draft'| E[Générer le site de brouillon]; D -->|Si 'main'| F[Générer le site de production]; E --> G[Stocker les artefacts]; F --> G[Stocker les artefacts]; G --> H{Déploiement}; H -->|Si 'draft'| I[Déploiement 'deploy-draft']; H -->|Si 'main'| J[Déploiement 'deploy-prod']; I --> K[Synchronisation 'rsync' vers le serveur 'draft']; J --> L[Synchronisation 'rsync' vers le serveur 'prod']; K --> M[Fin du pipeline]; L --> M[Fin du pipeline];

J’ai donc deux URL contact-able,

3. L’accès depuis Internet

Pour rendre accessible mon blog et au vu de la faible consommation en ressources d’un site statique, j’ai décidé de partir sur un VPS OVH premier prix. Le nom de domaine yanismercier.fr est aussi hébergé sur OVH.

Pour synchroniser les données entre mon CI/CD et le VPS j’utilise rsync via SSH avec une clé privé stocker dans les fichiers sécurisé de mon repositories Gitlab.

Pour assurer le service de mes deux sites statiques, j’utilise docker et plus précisément docker-compose. Docker-Compose permet de rendre mon infrastructure ** “déclarée”** et non “impérative”.

L’approche impérative consiste à exécuter des commandes une par une pour construire et gérer les conteneurs (par exemple, docker run). À l’inverse, l’approche déclarative utilise un fichier de configuration (comme docker-compose.yml ou un Dockerfile) pour décrire l’état final souhaité du service.

L’approche déclarative est selon moi toujours la voie à suivre car reproductible et versionnable

Arborescence de mon projet (draft et prod)

Arborescence de mon projet

name: mbs
services:
  prod:
    image: nginx:alpine
    volumes:
      - ./prod:/usr/share/nginx/html
    networks:
      - traefik_proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mbs-prod.rule=Host(`mbs.yanismercier.fr`) || Host(`blog.yanismercier.fr`)"
      - "traefik.http.routers.mbs-prod.entrypoints=websecure"
      - "traefik.http.routers.mbs-prod.tls.certresolver=myresolver"
      - "traefik.http.services.mbs-prod.loadbalancer.server.port=80"

  draft:
    image: nginx:alpine
    volumes:
      - ./draft:/usr/share/nginx/html
    networks:
      - traefik_proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mbs-draft.rule=Host(`mbs-draft.yanismercier.fr`) || Host(`blog-draft.yanismercier.fr`)"
      - "traefik.http.routers.mbs-draft.entrypoints=websecure"
      - "traefik.http.routers.mbs-draft.tls.certresolver=myresolver"
      - "traefik.http.services.mbs-draft.loadbalancer.server.port=80"

networks:
  traefik_proxy:
    external: true

Pour assurer la liaison des deux site sur le même port j’utilise Traefik comme reverse proxy. Traefik va aussi géré la certifications de mes blog de manière automatisé.

Voici un diagramme de comment le flux est géré dans mon VPS.

flowchart LR A@{ shape: odd, label: "Internet" } B(mon-VPS) A e1@--> B B e2@-->C subgraph in-docker C["`Traefik *choix du routage selon le nom DNS d'accès*`"] D(mbs.yanismercier.fr) E(mbs-draft.ymercier.fr) C e3@-->D C e4@-->E end e1@{ animation: fast } e2@{ animation: fast } e3@{ animation: slow } e4@{ animation: slow }

Voici mon fichier de stack pour mon reverse proxy:

services:
  traefik:
    image: "traefik:v3.5"
    container_name: "traefik"
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=placeholder"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "443:443"
    networks:
      -  proxy
    restart: always
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

networks:
  proxy:

Dans command: je viens ici paramétrer diverses options de mon Traefik dont le point d’accès, le système de challenge pour letsencrypt et le service discovery. Cette approche me permet d’éviter de gérer d’autres fichiers de configuration et d’avoir une déclaration unie de ce logiciel.

Pour plus d’informations sur les challenges et sur tout simplement comment fonctionne let’s encrypt, je vous invite à aller étudier leurs documentations (qui valent leurs coups 💙). –https://letsencrypt.org/how-it-works/--