<!--
  canonical-url: https://www.avonture.be/fr/blog/running-revealjs-with-docker
  published:     2025-12-15
  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.
-->

# Améliorez vos présentations avec Quarto, reveal.js, Decktape, Docker et les DevContainers

> Créez un slideshow reveal.js avec Quarto, Decktape, Docker et les DevContainers. Écrivez votre contenu en Markdown. Exportez-le en HTML et en PDF (grâce à Decktape)

---
![Améliorez vos présentations avec Quarto, reveal.js, Docker et les DevContainers](/img/v2/quarto_reveal_docker.webp)

**TL;DR**

Cet article détaille comment créer et gérer des présentations reveal.js avec Markdown, Quarto, Docker et les DevContainers de VSCode. Il montre comment mettre en place un projet Quarto dans un devcontainer pour écrire ses slides en Markdown, les prévisualiser en direct et voir les modifications se répercuter automatiquement. Le workflow couvre aussi l'export de la présentation en site HTML statique et en PDF de qualité grâce à Decktape : une alternative fluide aux outils traditionnels.

Cela fait des années, peut-être huit, que je n'ai plus utilisé PowerPoint pour une présentation. Cliquer, glisser, redimensionner, changer la résolution des slides, mince, tout recommencer... Dessiner des zones de texte, jouer avec les polices et les tailles, déplacer les éléments... Ah non, ça suffit ! Fini PowerPoint. J'en tremble encore.

J'utilise Markdown pour toute ma documentation et mes exports (PDF, DOCX, etc.), il était donc logique de choisir [reveal.js](https://revealjs.com/), qui me permet d'écrire mes slides sans toucher la souris, juste en écrivant du Markdown.

**Quick Jump:** [Voir le résultat](#the-result) | [Installation](#installation)

## Le résultat {#the-result}

Une fois le devcontainer construit et le serveur de preview démarré (voir la section Installation ci-dessous), votre slideshow s'ouvre en direct dans le navigateur — écrit en Markdown, prévisualisé instantanément :

![Le slideshow tourne](./images/first_slide.webp)

Facile, non ?

## Pourquoi ça fonctionne {#why-it-works}

reveal.js est beau, simple et pratique. Il permet d'écrire des slideshows HTML depuis un seul fichier `.md`. Comme j'utilise [Quarto](https://quarto.org/) pour ma documentation et que Quarto sait générer un site reveal.js, quoi de plus naturel que de prolonger [un de mes articles précédents](/blog/quarto-devcontainer) et de me fixer cet objectif : avec seulement VSCode et Docker sur ma machine, programmer un devcontainer qui me permette d'écrire et de visualiser ma présentation reveal.js, mais aussi de l'exporter en site HTML statique pour pouvoir, par exemple, déployer ma présentation sur un serveur SFTP, et enfin de générer une version PDF de la présentation où chaque slide correspond à une page en mode paysage.

* Quarto génère, depuis la même source Markdown, un slideshow reveal.js, un document Word, un PDF ou [une présentation PowerPoint](/blog/quarto-powerpoint) — une source, plusieurs sorties.
* Le devcontainer isole toute la toolchain (Quarto, le serveur de preview, Decktape) — rien à installer sur l'host à part VSCode et Docker.
* L'export en HTML ou en PDF de qualité impression est intégré via Decktape — aucun outil d'export supplémentaire à configurer.

Partons à l'aventure.

## Installation {#installation}

Assurez-vous donc d'avoir VSCode et Docker sur votre machine.

### Il nous faut un projet Quarto {#we-need-a-quarto-project}

Pour cet article, l'idée est d'utiliser Quarto et non de créer un projet reveal.js directement *à la main*. En effet, Quarto nous permettra de faire bien plus de choses, comme exporter notre slideshow en document Word, en PDF ou même en [présentation PowerPoint](/blog/quarto-powerpoint), ou vers n'importe quel autre format supporté par Quarto.

Nous utiliserons Quarto pour, aussi, pouvoir générer le projet sous forme de slideshow.

#### Vous n'en avez pas encore {#you-dont-have-one-yet}

Pour illustrer cet article, téléchargez le fichier [demo.zip](./files/demo.zip) que j'ai préparé. C'est un projet de slideshow prêt à l'emploi.

Enregistrez le fichier dans un nouveau dossier sur votre disque, par exemple `/tmp/revealjs`, et dézippez-le (`unzip demo.zip`). Ensuite, rendez-vous dans le dossier fraîchement créé (`cd /tmp/revealjs/demo`) et lancez `code .` pour ouvrir le projet dans VSCode.

Dans ce projet de démo, vous trouverez déjà un dossier `.devcontainer` avec les fichiers nécessaires que je décris au chapitre suivant.

#### Vous en avez déjà un {#you-have-one}

Si vous avez déjà un projet Quarto existant, rendez-vous dedans et lancez `code .` pour ouvrir le projet dans VSCode.

Ajoutez ensuite les fichiers suivants à la racine de votre projet :

**Project: /tmp/revealjs/demo**

> **Note:**
>
> Lancez maintenant 'code .' pour ouvrir VSCode. Appuyez sur 'F1' et sélectionnez 'Dev containers: Rebuild and Reopen in Devcontainer' pour ouvrir le projet en tant que devcontainer

**\_quarto.yml**

```yaml
project:
  type: default
  render: ["*.qmd"]

format:
  revealjs:
    css: style.css
    author: You
    ratio: "16:9"
    code-line-numbers: false
    center: true
    footer: https://www.yoursite.com/
    navigation-mode: linear
    transition: convex
    preview-links: auto
    scrollable: true
    show-notes: false
    slide-level: 6
    slide-number: c/t
    theme: default
    title-slide-attributes:
      data-background-image: images/background.webp
      data-background-size: cover
```

**.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: "false"
        INSTALL_CSPELL: "false"
        INSTALL_PRECOMMIT_HOOKS: "false"

    user: root

    working_dir: /workspace

    volumes:
      - ..:/workspace:rw
      - ./history/.bash_history:/home/vscode/.bash_history:rw
      - /var/run/docker.sock:/var/run/docker.sock
      - 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/devcontainer.json**

```json
{
  "name": "Quarto Docs Project - Demo",
  "dockerComposeFile": "compose.yaml",
  "service": "quarto-doc-base",
  "workspaceFolder": "/workspace",
  "remoteUser": "vscode",
  "containerEnv": {
    "LOCAL_WORKSPACE": "${localWorkspaceFolder}"
  },
  "features": {
    "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
      "version": "latest",
      "enableNonRootDocker": "true",
      "moby": "true"
    },
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/paul-gilber/devcontainer-features/yamllint-homebrew:1": {}
  },
  "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, 5931],
  "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"
      ]
    }
  }
}
```

**.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_PRECOMMIT_HOOKS="false"

# Yaml tooling
ARG YQ_VERSION=v4.42.1

ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# ---------- Stage 1: Install system dependencies ----------
#
# texlive-*       : to render as PDF using "quarto render . --to pdf"
# texlive-luatex  : to ensure lualatex is installed
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 \
        libnss3 libfontconfig1 libgbm1 \
        libsecret-1-dev \
        libx11-dev \
        libxkbfile-dev \
        openssh-client \
        sudo \
        unzip \
        # yamllint \
    # --- PDF Rendering Packages (Revised) ---
        texlive-xetex \
        texlive-fonts-recommended \
        texlive-plain-generic \
        texlive-luatex \
        texlive-latex-extra \
        texlive-fonts-extra \
        texlive-science \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Pre-generate the LuaLaTeX font database to avoid runtime delay
# This command must be run AFTER all texlive packages are installed.
# RUN set -eux; \
#     luaotfload-tool -f -u -v

# 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_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; \
        echo "Installing NPM and Code Spell Checker..." && \
        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

# ---------- 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}"

# Grant passwordless sudo access to the node user
# Allow our node user to run "sudo xxx" in case of very specific use case
RUN set -eux && \
    echo "${USERNAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/"${USERNAME}" && \
    chmod 0440 /etc/sudoers.d/"${USERNAME}"

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

# Note: Decktape is started on the host (using Docker-out-of-Docker). We need to
# make sure the slides.pdf file is created with the right ownership and in the right place; our local absolute path.
RUN set -eux \
    && echo "alias decktape='docker run --rm --user root -v \${LOCAL_WORKSPACE}/:/slides ghcr.io/astefanutti/decktape reveal http://host.docker.internal:5931/ slides.pdf && sudo chown ${USERNAME}:${USERNAME} slides.pdf'" >> "/home/${USERNAME}/.bashrc" \
    && echo "alias cspell='cspell lint . --config .vscode/cspell.json'" >> "/home/${USERNAME}/.bashrc" \
    && echo "alias pre-commit='pre-commit run --all-files'" >> "/home/${USERNAME}/.bashrc"

# 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"

# 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 "Below are some useful commands:"
echo -e ""
echo -e "  \e[1mQuarto Commands\e[0m"
echo -e "  ───────────────"
echo -e "  ✨ \e[32mquarto preview . --port 5931 --host 0.0.0.0\e[0m  Start a hot-reloading preview in your browser."
echo -e "  ✨ \e[32mquarto render . --output-dir build\e[0m           Render a static HTML site in the folder build."
echo -e ""
echo -e "  ✨ \e[32mdecktape\e[0m                                     Export the slideshow as slides.pdf with full respect of the layout."
echo -e ""
EOF

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

> **Les images ci-dessous sont celles de mon projet ; certaines seront forcément différentes chez vous.:**

### Ouvrir le devcontainer {#open-the-devcontainer}

Dans VSCode, appuyez sur `F1` pour ouvrir la **Command Palette** puis sélectionnez **Dev containers: Rebuild and Reopen in Devcontainer**. *Si vous n'avez pas cette commande, installez l'extension VSCode [Dev Container de Microsoft](https://marketplace.visualstudio.com/publishers/Microsoft).*

VSCode va donc *reconstruire et réouvrir le projet en tant que devcontainer* : autrement dit, VSCode va d'abord construire une image Docker spécifique (telle que décrite dans le fichier `.devcontainer/Dockerfile`) et probablement télécharger d'abord l'image Docker de Quarto ; puis, une fois l'image créée, VSCode va créer un container basé sur cette image et enfin réouvrir le projet dans un environnement particulier appelé *un devcontainer*.

Le container va se construire (comptez 1 à 2 minutes la première fois).

Une fois le build terminé, VS Code affiche un écran d'accueil comme celui-ci :

![Construction du devcontainer](./images/building_devcontainer.webp)

En cliquant sur le lien `Connecting to Dev Container (show log)`, vous obtiendrez plus de détails et pourrez suivre ce qui se passe.

Après une ou deux minutes (cela n'arrive que la première fois), vous obtiendrez un écran comme celui-ci :

![Liste des commandes](./images/cheatsheet.webp)

> **Cliquez sur le bouton `+`:**
>
> Si vous n'avez pas l'écran **Terminal** comme sur l'image ci-dessus, regardez à droite de l'écran et cliquez sur le bouton `+` pour créer un nouveau terminal bash.

## Plus de démos {#more-demos}

### Prévisualiser le slideshow {#preview-the-slideshow}

Regardez la liste des commandes affichée dans le Terminal : il y en a une pour la preview. Lancez-la simplement comme suggéré, donc `quarto preview . --port 5931 --host 0.0.0.0`.

```bash
quarto preview . --port 5931 --host 0.0.0.0
```

Appuyez sur `Enter` pour démarrer le serveur de preview. VS Code vous demandera l'autorisation d'ouvrir le site dans votre navigateur.

![Ouverture du site](./images/external_website.webp)

Et, presque immédiatement, VSCode lancera votre navigateur et créera un nouvel onglet avec votre slideshow déjà actif — le même résultat que celui montré en haut de cet article.

#### Mettre à jour le slideshow {#updating-the-slideshow}

Comme vous le voyez, mon slideshow est en français. Traduisons-le.

Utilisez l'outil de traduction que vous voulez, moi j'utilise [DeepL](https://www.deepl.com/). Copiez un peu de contenu et traduisez-le en anglais. Retournez sur votre slideshow, rafraîchissez la page et... tadaaaaa, votre contenu est maintenant en anglais.

![Slide traduite](./images/translated_slide.webp)

Comme nous venons de le voir, les modifications de votre contenu sont automatiquement répercutées dans le navigateur.

> **Aller plus loin avec reveal.js:**
>
> Une fois votre slideshow lancé, jetez un œil à [Some tips and tricks for Quarto when rendering as a reveal.js slideshow](/blog/quarto-revealjs-tips) pour affiner le rendu.

### Exporter un PDF {#export-a-pdf}

Maintenant que votre slideshow tourne, retournez dans votre Terminal (appuyez sur `CTRL`+`ù` pour l'ouvrir) et créez une nouvelle session Bash (cliquez sur le bouton `+`).

Là, lancez `decktape` dans le Terminal pour générer un fichier appelé `slides.pdf` :

```bash
$ decktape
```

Cela va générer un joli fichier PDF (bien meilleur que celui produit par la fonction d'export PDF de reveal.js).

### Exporter en site HTML statique {#export-as-static-html-site}

En lançant `quarto render . --output-dir build`, vous créerez un dossier `build` contenant votre slideshow :

```bash
$ quarto render . --output-dir build
```

> **Envie d'essayer ?:**
>
> Sur votre host, rendez-vous dans le dossier `build` et lancez `docker run -d --name static-site -p 8080:80 -v .:/usr/local/apache2/htdocs/ httpd:alpine` pour [faire tourner le slideshow](/blog/docker-html-site) en dehors de votre devcontainer.
>
> C'est un moyen simple de vérifier que ça fonctionne.

Vous pouvez désormais lancer votre client SFTP, par exemple, et uploader le dossier `build` sur votre serveur web si c'était votre objectif.

## Conclusion {#conclusion}

Du Markdown en entrée, un slideshow prévisualisé en direct en sortie, et un PDF ou un site HTML statique quand vient le moment de livrer — tout le rituel PowerPoint (cliquer, glisser, redimensionner) a disparu, remplacé par le même workflow de devcontainer que celui utilisé pour tout le reste sur ce blog.

Si vous n'avez jamais mis en place un devcontainer Quarto, [Make your Quarto project Devcontainer-Ready — No More Setup Headaches](/blog/quarto-devcontainer) détaille davantage ces fondations.
