<!--
  canonical-url: https://www.avonture.be/fr/blog/quarto-devcontainer
  published:     2025-11-03
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Rendez votre projet Quarto prêt pour les Devcontainers — fini les galères de configuration

> Convertissez n'importe quel projet Quarto en devcontainer totalement portable et piloté par VSCode, en quelques minutes.

---
![Rendez votre projet Quarto prêt pour les Devcontainers — fini les galères de configuration](/img/v2/devcontainer_quarto.webp)

**TL;DR**

Cet article explique comment convertir n'importe quel projet Quarto en devcontainer portable piloté par VSCode, pour éliminer les galères de configuration. En ajoutant trois fichiers de configuration précis (`compose.yaml`, `Dockerfile`, `devcontainer.json`), vous obtenez un environnement de développement complet. Cette configuration inclut Quarto préinstallé, des extensions VSCode configurées, ainsi que des fonctionnalités comme la prévisualisation automatique et des pre-commit hooks optionnels, ce qui simplifie le workflow de documentation.

J'adore utiliser Quarto et j'ai des tonnes de projets de *documentation* sur mon disque. J'écris de la documentation utilisateur, de la documentation développeur et aussi des guides d'installation pour quasiment chaque projet que je code.

Si vous n'avez jamais lancé Quarto via Docker, [Running Quarto Markdown in Docker](/blog/docker-quarto) est un bon point de départ ; cet article part de cette idée pour vous offrir un devcontainer VSCode complet.

L'idée m'est vite venue de créer une image Docker pour pouvoir développer ma documentation tranquillement, ainsi qu'une configuration devcontainer pour VSCode. Maintenant, quand je veux écrire ma documentation, en moins d'une minute, je peux créer un environnement entièrement dédié à la rédaction de documentation.

Entre autres, j'installe Quarto, j'installe et configure plusieurs extensions pour VSCode, et je lance Quarto en mode preview : dès que j'enregistre une modification, Quarto régénère la documentation tout seul.

Voyons comment faire ; vous allez voir, c'est vraiment simple.

*Le même devcontainer, spécialisé pour les diaporamas, est utilisé dans [Level Up Your Presentations with Quarto, reveal.js, Decktape, Docker and DevContainers](/blog/running-revealjs-with-docker) ; et poussé à l'échelle industrielle dans [Quarto - How I Built a Self-Documenting Ecosystem for 50+ Projects](/blog/quarto-industrialisation).*

**Quick Jump:** [Commençons par un projet Quarto simple](#lets-get-a-simple-quarto-project-first) | [Nous allons devoir créer trois fichiers](#well-need-to-create-three-files)

## Commençons par un projet Quarto simple {#lets-get-a-simple-quarto-project-first}

Bien sûr, si vous en avez déjà un, allez directement dans votre projet. Ici, pour ce billet, nous allons simplement réutiliser un projet existant.

En lançant les commandes ci-dessous dans une console, vous téléchargerez uniquement le dossier `brand` du projet `quarto-examples` :

```bash
$ cd /tmp
git clone --filter=blob:none --sparse https://github.com/quarto-dev/quarto-examples
cd quarto-examples
git sparse-checkout set brand
cd brand/brand-simple
```

Lancez `code .` pour ouvrir le projet dans VSCode et vous obtiendrez ceci :

![Ouverture du projet brand-simple dans VSCode](./images/opening-in-vscode.webp)

Voici où nous allons : ouvrir ce même projet comme devcontainer, lancer `quarto preview .`, et le site tourne déjà — sans aucune installation de Quarto sur votre host.

![Lancement de quarto preview](./images/running_in_preview_mode.webp)

![Le site tourne déjà](./images/preview.webp)

Ridiculement simple, non ? Construisons ça, en commençant par trois fichiers.

## Nous allons devoir créer trois fichiers {#well-need-to-create-three-files}

Pour obtenir les meilleures performances en termes de vitesse de build de l'image Docker et d'utilisation du mécanisme de cache de Docker, nous allons devoir créer trois fichiers :

Le fichier `.devcontainer/compose.yaml` est nécessaire pour dire à VSCode de créer notre image Docker si elle n'existe pas encore, ou de la réutiliser si elle existe. Ce fichier est donc essentiel côté performances.

Le fichier `.devcontainer/Dockerfile` définit notre image Docker : les binaires dont nous avons besoin, la configuration utilisateur, etc.

Le fichier `.devcontainer/devcontainer.json` est utilisé par VSCode pour comprendre et construire notre environnement de travail.

**Project: /tmp/quarto-examples/brand/brand-simple**

**.devcontainer/compose.yaml**

```yaml
---
# cspell:ignore tinytex

services:
  quarto-doc-base:
    image: quarto-devcontainer:latest

    build:
      context: .
      dockerfile: Dockerfile
      args:
        # We use default values here. The actual UID/GID mapping is handled
        # by the VS Code Dev Container CLI at runtime, overriding these defaults
        # when the container starts via the user: vscode directive in the Dockerfile.
        USER_UID: 1000
        USER_GID: 1000
        # Flags you'll need to adjust depending on your project
        INSTALL_CHROMIUM: "true"
        INSTALL_CSPELL: "false"
        INSTALL_GRAPHVIZ: "true"
        INSTALL_PRECOMMIT_HOOKS: "false"

    user: vscode

    working_dir: /workspace

    volumes:
      - ..:/workspace:cached
      - ./history/.bash_history:/home/vscode/.bash_history:rw
      - type: bind
        source: ${HOME}/.quarto
        target: /home/vscode/.quarto
      - type: bind
        source: ${HOME}/.ssh/id_ed25519
        target: /home/vscode/.ssh/id_ed25519
        read_only: true

    # Base command to keep the container running indefinitely
    command: /bin/sh -c "while sleep 1000; do :; done"
```

**.devcontainer/Dockerfile**

```docker
# syntax=docker/dockerfile:1.4

# cspell:disable
FROM ghcr.io/quarto-dev/quarto:1.8.25 AS base

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=1000

ARG INSTALL_CHROMIUM=false
ARG INSTALL_CSPELL=false
ARG INSTALL_GRAPHVIZ=false
ARG INSTALL_PRECOMMIT_HOOKS=false

# Yaml tooling
ARG YQ_VERSION=v4.42.1

# ---------- Stage 1: Install system dependencies ----------
#
# openssh-client  : used by git when pushing/pulling using git@ protocol
# sudo            : to easily install additional dependencies if needed
# texlive-*       : to render as PDF using "quarto render . --to pdf"
# yamllint        : to validate YAML frontmatter in .qmd files
RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    set -eux; \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
        git \
        libnss3 libfontconfig1 libgbm1 \
        libsecret-1-dev \
        libx11-dev \
        libxkbfile-dev \
        openssh-client \
        texlive-xetex texlive-fonts-recommended texlive-plain-generic \
        unzip \
        sudo \
        yamllint \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Allow our user to run sudo without password
RUN echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Install yq (YAML validator) for validating YAML frontmatter
RUN --mount=type=cache,target=/var/cache/yq \
    set -eux; \
    curl -L --fail --silent --show-error \
        https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 \
        -o /var/cache/yq/yq_linux_amd64; \
    install -m 0755 /var/cache/yq/yq_linux_amd64 /usr/local/bin/yq

# ---------- Stage 2: Extras - pre-commit, Node.js + cspell ----------
FROM base AS extras

ARG INSTALL_CSPELL
ARG INSTALL_GRAPHVIZ
ARG INSTALL_PRECOMMIT_HOOKS
ARG USERNAME

# Install pre-commit (without installing hooks at build time)
RUN --mount=type=cache,target=/root/.cache/pip \
    if [ "$INSTALL_PRECOMMIT_HOOKS" = "true" ]; then \
        set -eux; \
        echo "Installing Python and pre-commit..." && \
        apt-get update && \
        apt-get install -y --no-install-recommends python3-pip && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* && \
        pip3 install --no-cache-dir --upgrade pip && \
        pip3 install --no-cache-dir pre-commit && \
        pip cache purge || true; \
    fi

# Install Node.js + cspell (optional)
RUN --mount=type=cache,target=/home/${USERNAME}/.npm \
    if [ "${INSTALL_CSPELL}" = "true" ]; then \
        set -eux; \
        curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
        apt-get install -y --no-install-recommends nodejs && \
        npm config set cache /home/${USERNAME}/.npm && \
        npm install -g cspell && \
        npm cache clean --force && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/*; \
    fi

# Install Graphviz for Quarto (dot diagrams support)
RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    if [ "${INSTALL_GRAPHVIZ}" = "true" ]; then \
        set -eux; \
        apt-get update \
        && apt-get install -y --no-install-recommends \
            libcairo2 \
            libpango-1.0-0 \
            graphviz \
        && apt-get clean \
        && rm -rf /var/lib/apt/lists/*; \
    fi

# ---------- Stage 3: Finalization ----------
FROM extras AS final

ARG USERNAME
ARG USER_UID
ARG USER_GID
ARG INSTALL_CHROMIUM

# ---------- User and permissions --------------------------------------------
RUN set -eux && \
    USER_UID=${USER_UID:-1000} && \
    USER_GID=${USER_GID:-1000} && \
    groupadd -g "${USER_GID}" "${USERNAME}" && \
    useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash "${USERNAME}" && \
    chown -R "${USERNAME}":"${USERNAME}" "/home/${USERNAME}"

# Create .ssh directory and set permissions for the non-root user
RUN set -eux && \
    mkdir -p /home/${USERNAME}/.ssh && \
    chown -R "${USERNAME}":"${USERNAME}" "/home/${USERNAME}/.ssh" && \
    chmod 700 /home/${USERNAME}/.ssh

# Switch to non-root user for Chromium and Quarto tools
USER ${USERNAME}

# Install Quarto Chromium tool (optional)
RUN --mount=type=cache,target=/home/${USERNAME}/.quarto/tools \
    if [ "${INSTALL_CHROMIUM}" = "true" ]; then \
        set -eux; \
        echo "Installing Quarto's Chromium tool..." && \
        quarto install tool chromium; \
    fi

# ---------- Shell and history setup -----------------------------------------
# Configure bash to use this file
RUN echo "export HISTFILE=/home/${USERNAME}/.bash_history" >> "/home/${USERNAME}/.bashrc" && \
    echo 'export HISTSIZE=10000' >> "/home/${USERNAME}/.bashrc" && \
    echo 'export HISTFILESIZE=10000' >> "/home/${USERNAME}/.bashrc" && \
    echo 'shopt -s histappend' >> "/home/${USERNAME}/.bashrc" && \
    echo "alias render_graphviz='find . -name \"flow.dot\" -exec sh -c '\''for f; do dir=\$(dirname \"\$f\"); dot -Tpng \"\$f\" -o \"\$dir/flow.png\"; done'\'' sh {} +'" >> "/home/${USERNAME}/.bashrc"

# Set up the shell prompt
RUN set -eux && \
    echo "PS1='\n\e[0;33m🐳 \e[0;36m\$(whoami)\e[0m \w # '" >> "/home/${USERNAME}/.bashrc"

# Welcome message and tips
RUN set -eux && \
    cat <<EOF >> "/home/${USERNAME}/.bashrc"
echo -e "\n📘 Welcome to the Quarto Docs Dev Container!"
echo -e ""
echo -e "Below are some useful commands:"
echo -e ""
echo -e "  \e[1mQuarto Commands\e[0m"
echo -e "  ───────────────"
echo -e "  ✨ \e[32mquarto preview .\e[0m                             Start a hot-reloading preview in your browser."
echo -e "  ✨ \e[32mquarto render .\e[0m                              Build the documentation (based on _quarto.yml)."
echo -e "  ✨ \e[32mquarto render . --profile docx --to docx\e[0m     Build a .docx file (requires _quarto-docx.yml)."
EOF

# Append conditional commands depending on optional tools
RUN set -eux && \
    if [ "${INSTALL_CSPELL}" = "true" ] || [ "${INSTALL_PRECOMMIT_HOOKS}" = "true" ]; then \
        echo -e 'echo -e ""' >> "/home/${USERNAME}/.bashrc"; \
        echo -e 'echo -e "  \e[1mData quality checks\e[0m"' >> "/home/${USERNAME}/.bashrc"; \
        echo -e 'echo -e "  ───────────────────"' >> "/home/${USERNAME}/.bashrc"; \
    fi && \
    if [ "${INSTALL_CSPELL}" = "true" ]; then \
        echo 'echo -e "  ✍️  \e[32mcspell lint . --config .vscode/cspell.json\e[0m  Run spell checks.\n"' >> "/home/${USERNAME}/.bashrc"; \
    fi && \
    if [ "${INSTALL_PRECOMMIT_HOOKS}" = "true" ]; then \
        echo 'echo -e "  ⚙️  \e[32mpre-commit run --all-files\e[0m                  Run code quality checks on all files.\n"' >> "/home/${USERNAME}/.bashrc"; \
    fi

# Tools
RUN set -eux && \
    if [ "${INSTALL_GRAPHVIZ}" = "true" ]; then \
        echo 'echo -e ""' >> "/home/${USERNAME}/.bashrc"; \
        echo 'echo -e "  \e[1mTools\e[0m"' >> "/home/${USERNAME}/.bashrc"; \
        echo 'echo -e "  ─────"' >> "/home/${USERNAME}/.bashrc"; \
        echo 'echo -e "  📊 \e[32mrender_graphviz\e[0m                              Scan your documentation, process .dot files and generate .png ones.\n"' >> "/home/${USERNAME}/.bashrc"; \
    fi
```

**.devcontainer/devcontainer.json**

```json
{
    "name": "Quarto Docs Project",
    "dockerComposeFile": "compose.yaml",
    "service": "quarto-doc-base",
    "workspaceFolder": "/workspace",
    "remoteUser": "vscode",
    "containerEnv": {
        "GIT_SSH_COMMAND": "ssh -i /home/vscode/.ssh/id_ed25519 -o 'UserKnownHostsFile=/dev/null'"
    },
    "initializeCommand": "mkdir -p .devcontainer/history && touch .devcontainer/history/.bash_history && echo '.bash_history' > .devcontainer/history/.gitignore",
    "postAttachCommand": "if which pre-commit >/dev/null 2>&1; then pre-commit install || true; fi",
    "forwardPorts": [
        8000
    ],
    "customizations": {
        "vscode": {
            "settings": {
                "[dockerfile]": {
                    "files.eol": "\n",
                    "editor.defaultFormatter": "ms-azuretools.vscode-docker"
                },
                "[json]": {
                    "editor.defaultFormatter": "vscode.json-language-features"
                },
                "[jsonc]": {
                    "editor.defaultFormatter": "vscode.json-language-features",
                    "editor.wordWrap": "wordWrapColumn",
                    "editor.wordWrapColumn": 80,
                    "editor.wrappingIndent": "indent"
                },
                "[markdown]": {
                    "editor.defaultFormatter": "yzhang.markdown-all-in-one",
                    "editor.wordWrap": "wordWrapColumn",
                    "editor.wordWrapColumn": 80,
                    "editor.wrappingIndent": "indent"
                },
                "[yaml]": {
                    "editor.defaultFormatter": "redhat.vscode-yaml"
                },
                "cSpell.language": "en,fr,nl",
                "cSpell.words": [
                    "Quarto",
                    "devcontainer",
                    "frontmatter",
                    "Pandoc",
                    "Gitlens"
                ],
                "editor.formatOnSave": true,
                "editor.guides.bracketPairs": "active",
                "editor.guides.bracketPairsHorizontal": "active",
                "editor.guides.highlightActiveIndentation": true,
                "editor.guides.indentation": true,
                "editor.multiCursorModifier": "ctrlCmd",
                "editor.renderWhitespace": "all",
                "editor.rulers": [
                    120,
                    70
                ],
                "editor.stickyScroll.enabled": true,
                "editor.tabCompletion": "on",
                "editor.tabSize": 4,
                "editor.wordBasedSuggestionsMode": "allDocuments",
                "editor.wordWrapColumn": 120,
                "explorer.compactFolders": false,
                "explorer.confirmDelete": true,
                "explorer.confirmDragAndDrop": true,
                "extensions.autoCheckUpdates": false,
                "files.associations": {
                    "resources/*.json": "jsonc"
                },
                "files.autoSave": "onFocusChange",
                "files.defaultLanguage": "${activeEditorLanguage}",
                "files.eol": "\n",
                "files.exclude": {
                    "**/.cache": true,
                    "**/.git": true,
                    "**/.quarto": true,
                    "**/*.html": true,
                    "**/readme_files/**": true,
                    "**/site_libs/**": true
                },
                "files.insertFinalNewline": true,
                "files.trimTrailingWhitespace": true,
                "maptz.regionfolder": {
                    "[dockerfile]": {
                        "foldEnd": "[\\S]*\\#[\\s]*endregion",
                        "foldEndRegex": "[\\S]*\\#[\\s]*endregion[\\s]*(.*)$",
                        "foldStart": "[\\S]*\\#[\\s]*region [NAME]",
                        "foldStartRegex": "[\\S]*\\#[\\s]*region[\\s]*(.*)$"
                    }
                },
                "markdown.extension.toc.levels": "2..6",
                "markdown.extension.tableFormatter.enabled": true,
                "markdownlint.config": {
                    "MD033": false,
                    "MD036": false
                },
                "quarto.preview.renderOnSave": true,
                "telemetry.telemetryLevel": "off",
                "terminal.integrated.profiles.linux": {
                    "bash": {
                        "path": "/bin/bash",
                        "icon": "terminal-bash"
                    }
                },
                "terminal.integrated.defaultProfile.linux": "bash",
                "terminal.integrated.fontFamily": "MesloLGS NF",
                "terminal.integrated.tabs.defaultLocation": "editorGroup",
                "yaml.schemas": {
                    "https://json.schemastore.org/json-schema-v7.json": [
                        "_quarto.yml",
                        "_quarto-*.yml"
                    ],
                    "https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml"
                },
                "window.title": "${dirty}${rootName} ${separator} [DevContainer]",
                "workbench.colorTheme": "Visual Studio Light"
            },
            "extensions": [
                "DavidAnson.vscode-markdownlint",
                "esbenp.prettier-vscode",
                "gitkraken.gitlens",
                "maptz.regionfolder",
                "mde.select-highlight-minimap",
                "mikestead.dotenv",
                "ms-vscode.makefile-tools",
                "quarto.quarto",
                "redhat.vscode-yaml",
                "sirtori.indenticator",
                "streetsidesoftware.code-spell-checker-dutch",
                "streetsidesoftware.code-spell-checker-french",
                "streetsidesoftware.code-spell-checker",
                "yzhang.markdown-all-in-one"
            ]
        }
    }
}
```

> **Information:**
>
> À strictement parler, nous n'avons pas besoin du fichier `.devcontainer/compose.yaml`, mais c'est le seul moyen de construire l'image Docker **une seule fois** et de la réutiliser sur plusieurs projets.
>
> En ouvrant un Devcontainer, même si le `Dockerfile` est strictement identique dans tous vos projets de documentation, le contexte sera différent (**project1**, **project2**, ...) et VSCode reconstruira l'image pour ce contexte. Pour éviter ça, il faut construire l'image et **lui donner un nom** ; ce n'est possible qu'avec un fichier `.devcontainer/compose.yaml`.

## Informations supplémentaires (optionnel — passez si les valeurs par défaut vous conviennent) {#extra-information-optional--skip-if-the-defaults-work-for-you}

### Installation de sudo {#installation-of-sudo}

Pendant la création de l'image Docker, nous installons aussi la commande `sudo` et autorisons notre utilisateur `vscode` à lancer `sudo su root` dans le devcontainer sans devoir saisir de mot de passe. La raison : parfois, Quarto se plaint d'une dépendance manquante (par exemple lors d'un export en PDF, quand une librairie manque). Pour permettre au développeur d'ajouter rapidement la dépendance et de faire quelques tests sans devoir reconstruire le container encore et encore, `sudo` est installé.

## Arguments de build (optionnel — ne lisez que ceux qui vous concernent) {#build-arguments-optional--read-only-the-ones-you-need}

### Installation de Chromium {#installation-of-chromium}

Si vous regardez le fichier `.devcontainer/compose.yaml`, vous verrez un argument appelé `INSTALL_CHROMIUM`. Selon votre projet, vous en aurez besoin ou pas.

En effet, dans certaines situations, lors du rendu de votre documentation en Word (c'est-à-dire en lançant par exemple `quarto render . --profile docx --to docx`), Quarto peut vous demander d'installer Chromium. Pour éviter de le faire chaque fois, ouvrez simplement le fichier `.devcontainer/devcontainer.json`, cherchez `INSTALL_CHROMIUM` et mettez-le à `true`.

Note : si vous modifiez le code du `.devcontainer/Dockerfile` ou le fichier `.devcontainer/devcontainer.json`, vous devrez reconstruire le container comme expliqué ci-dessous.

> **Assurez-vous d'en avoir besoin:**
>
> Avant d'installer Chromium, assurez-vous d'en avoir besoin : faites d'abord le rendu de votre documentation sans lui et voyez si Quarto se plaint de Chromium. Chromium demande beaucoup de dépendances et fera considérablement grossir la taille de votre image Docker.

### Installation de Code Spell Checker {#installation-of-code-spell-checker}

De la même manière, vous avez une variable appelée `INSTALL_CSPELL`. Mettez-la à `true` si vous voulez installer l'outil Code-spell check (nécessite Node.js).

### Installation de GraphViz {#installation-of-graphviz}

De la même manière, vous avez une variable appelée `INSTALL_GRAPHVIZ`. Mettez-la à `true` si vous voulez installer le convertisseur GraphViz.

C'est nécessaire quand, dans votre fichier `.qmd`, vous avez un graphe comme celui-ci :

```markdown
digraph flow {
  rankdir = LR;

  A [shape=box, label="Source"];
  B [shape=ellipse, label="Processor"];
  C [shape=box, label="Destination"];

  A -> B;
  B -> C;
}
```

> **GraphViz:**
>
> Si vous avez un graphe Graphviz et que vous n'avez pas installé GraphViz, vous obtiendrez une erreur comme celle-ci : `ERROR: AssertionError: Error occurred during cleanup: TypeError: Child process has already terminated`.

### Installation des pre-commit-hooks {#installation-of-pre-commit-hooks}

La troisième variable est `INSTALL_PRECOMMIT_HOOKS` et, si votre documentation a son propre dossier `.git`, ce sera une bonne idée de mettre la variable à `true` : ainsi, lors du commit de vos modifications, quelques contrôles de qualité des données et outils de formatage seront appliqués.

## Ouvrir notre projet comme Devcontainer {#opening-our-project-as-a-devcontainer}

Maintenant, appuyez juste sur `F1` (ou `CTRL`+`SHIFT`+`P`) et sélectionnez **Dev Containers: Rebuild and Reopen in Container**. Si vous n'avez pas cette commande, vérifiez que l'extension `ms-vscode-remote.remote-containers` de Microsoft est bien installée.

> **Le remote container de Microsoft:**
>
> Vous pouvez installer l'extension depuis la console en lançant `code --install-extension ms-vscode-remote.remote-containers` ou en ouvrant cette page : [Marketplace - Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).

Donc, une fois la commande **Dev Containers: Rebuild and Reopen in Container** lancée, VSCode fermera votre session courante et en ouvrira une nouvelle. La toute première fois, VSCode devra faire quelques initialisations (comme construire l'image Docker et télécharger les extensions, ce sera donc plus lent).

Après quelques secondes, vous obtiendrez un écran comme celui-ci :

![Le Devcontainer a été ouvert](./images/devcontainer.webp)

1. En bas à gauche, dans la barre de statut, vous avez le texte "Dev Container: xxx" (suivi du nom de notre container tel que configuré dans le fichier `devcontainer.json`).
2. Dans la **fenêtre Terminal** (si vous ne la voyez pas, appuyez sur `CTRL`+`ù`), vous verrez un pense-bête avec les commandes principales, comme `quarto preview .` pour lancer le site avec le hot reload.

Donc, toujours dans le Terminal, tapez `quarto preview .` puis appuyez sur `Enter` — vous obtiendrez la même sortie que celle déjà montrée en haut de cet article.

En théorie, votre navigateur sera lancé automatiquement et le site ouvert, affichant le même résultat que celui déjà montré plus haut. Sinon, suivez simplement le lien en appuyant sur `ALT` et en cliquant dessus.

Ridiculement simple, non ?

## Qu'avons-nous fait ? {#what-have-we-done}

À ce stade, qu'avons-nous fait ? Nous avons récupéré un projet de site Quarto et nous nous sommes assurés de le « dockeriser ».

Regardons le fichier `_quarto.yml` du projet que nous venons de cloner (vous trouverez plus d'informations sur le site officiel de Quarto : [Creating a website](https://quarto.org/docs/websites/)) :

**\_quarto.yml**

```yaml
project:
  type: website

website:
  title: "Simple Brand Example"
  navbar:
    title: "Demo CO"
    tools:
      - icon: github
        href: https://github.com/quarto-dev/quarto-examples/tree/main/brand/brand-simple
```

Nous avons ajouté trois fichiers au projet, dans un dossier spécial appelé `.devcontainer`. Ce dossier indique à VSCode que nous souhaitons utiliser un container Docker quand nous travaillons sur ce projet. Le container sera basé sur une image Docker personnalisée (telle que codée dans `.devcontainer/Dockerfile`). L'image réutilisera l'image officielle de Quarto.

Ainsi, sans devoir installer Quarto sur notre machine, nous pourrons utiliser toutes les fonctionnalités de Quarto.

Enfin, nous devons *basculer vers le devcontainer* : juste après avoir ouvert VSCode, nous devons lancer une commande spéciale pour rouvrir le projet comme devcontainer.

Cette commande est, la première fois, **Dev Containers: Rebuild and Reopen in Container** mais, dès que l'image personnalisée a été créée une fois, nous pouvons simplement utiliser **Dev Containers: Reopen in Container**.

## Rédiger notre documentation {#writing-our-documentation}

Jusqu'ici, nous avons déjà lancé `quarto preview .` dans un terminal. La documentation a été générée et notre site est déjà affiché (sur `http://localhost:7519` ou tout autre port).

Testons le hot reload : éditez le fichier `index.qmd` que vous trouverez dans le dossier racine et changez quelque chose. Par exemple, remplacez le mot `Overview` par `Preface` et enregistrez.

![La synchronisation est déjà activée](./images/synchronization-enabled.webp)

Comme vous le voyez sur l'image ci-dessus, il me suffit de faire une modification, d'enregistrer mon fichier et d'attendre moins d'une seconde pour que le site soit mis à jour automatiquement. Rien à faire (je n'ai pas besoin de rafraîchir la page). Simple, non ?

## Utiliser le terminal {#using-the-terminal}

Allez dans la **fenêtre Terminal** (si vous ne la voyez pas, appuyez sur `CTRL`+`ù`).

Regardez maintenant la partie droite : il y a un terminal `Configuring...` mais aussi un `bash workspace` (le nom de notre projet). Si vous ne le voyez pas, regardez juste au-dessus de `Configuring...` et cliquez sur le bouton `+`.

![Le terminal](./images/terminal.webp)

Comme vous le voyez, vous avez quelques *instructions*. Ces instructions ont été codées dans la dernière partie du fichier `.devcontainer/Dockerfile`. Elles sont là juste pour aider l'utilisateur la première fois.

Donc, si votre objectif est de générer la documentation finale, vous pouvez toujours vous dire *Ah oui, il me suffit de lancer `quarto render .`*

## Qu'est-ce que pre-commit ? {#what-is-pre-commit}

Dans ce projet, j'ai prévu l'installation de [pre-commit](https://pre-commit.com/). C'est un outil optionnel qui lancera quelques contrôles sur votre projet avant que vous ne le poussiez vers votre système de versioning (comme GitHub ou GitLab). *Je lui ai consacré un article complet : [Git - pre-commit-hooks](/blog/git-precommit).*

En ajoutant `pre-commit` dans le devcontainer, vous aurez le privilège de ne plus jamais commiter de fichiers avec des défauts comme, par exemple, des problèmes de formatage dans votre contenu Markdown.

Si cette idée vous plaît, créez le fichier `.pre-commit-config.yaml` dans le dossier racine de votre projet (donc pas dans le dossier `.devcontainer` mais dans son dossier parent).

Copiez/collez simplement le contenu ci-dessous :

**.pre-commit-config.yaml**

```yaml
# Pre-commit configuration for Quarto projects
exclude: "^.build/|^.git/|^.quarto/"

repos:
  # --- Universal hooks for clean files ---
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: check-added-large-files
      - id: check-case-conflict
      - id: check-json
      - id: check-merge-conflict
      - id: check-yaml
      - id: detect-private-key
      - id: end-of-file-fixer
      - id: trailing-whitespace

  # Markdown linter
  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.45.0
    hooks:
      - id: markdownlint
        args:
          - "--config=.config/.markdownlint.yaml"

  # Checks all of the hyperlinks in a markdown text to determine if they are alive or dead
  - repo: https://github.com/tcort/markdown-link-check
    rev: v3.13.7
    hooks:
      - id: markdown-link-check
        name: Check Markdown Links
        entry: markdown-link-check
        language: node
        types: [markdown]

  # The hook below hook extracts the YAML front matter from each .qmd
  # file and validates its syntax using yq.
  # It helps catch formatting errors (e.g. missing colons, invalid keys)
  # before committing.
  # Only the YAML block between the first pair of --- lines is checked.
  - repo: local
    hooks:
      - id: validate-qmd-yaml
        name: Validate YAML front matter in .qmd
        entry: bash -c 'awk "/^---/{f=!f;next}f" "$1" | yq eval "." - > /dev/null'
        language: system
        pass_filenames: true
        args: ["{}"]
        files: \.qmd$

  # --- Spelling ---
  # Requires to install cspell in the Docker file as a binary. This implies to install NPM tool
  # The .vscode/cspell.json file is shared with VSCode
  - repo: local
    hooks:
      - id: cspell
        name: Spell check with cspell
        entry: cspell
        language: system
        args:
          - "--config=.vscode/cspell.json"
          - "--no-progress"
          - "--no-summary"
        exclude: ^.*_extensions/.*
```

Une fois ce fichier en place, quand vous lancerez la commande `git commit` depuis le terminal (`CTRL`+`ù`), `git` lancera d'abord `pre-commit` pour exécuter les *hooks*. Dans le fichier `.pre-commit-config.yaml` ci-dessus, nous avons défini deux hooks : le premier pour faire des vérifications génériques sur les fichiers, puis un second pour s'assurer qu'il n'y a pas d'erreurs de linting dans les fichiers `.md` (consultez le repo officiel pour plus d'infos).

Ces hooks sont déclenchés chaque fois que vous commitez des fichiers (et uniquement sur les fichiers commités ; pas sur tout le projet).

Vous pouvez aussi forcer l'exécution des hooks sans commiter, en lançant `pre-commit run --all-files` dans la console.

Cette commande vérifiera tous les fichiers.

`pre-commit` est un outil précieux pour garantir le respect des standards de qualité établis dans l'industrie.

## Conclusion {#conclusion}

Avec seulement trois fichiers (`.devcontainer/compose.yaml`, `.devcontainer/Dockerfile` et `.devcontainer/devcontainer.json`), nous avons converti un projet simple en Devcontainer où Quarto est préinstallé, les extensions installées et configurées, et où des fonctionnalités comme la prévisualisation automatique tournent.

Cool, non ?
