Code
On this page we will:
- See how to add code cells to a Quarto document.
- Learn how to deploy sites via GitHub actions when they contain executable code.
1 Code in Quarto pages
In a Quarto document, you can include code cells and choose whether they run when you render the site.
1.1 Executable code
A code cell is a fenced block like:
```{python}
print("Hello from Python")
```If the cell has a language (for example {python}, {r}, {bash}), Quarto can execute it when you render.
That means Quarto actually runs the code, captures the output (and any figures), and inserts those results into the page. This is powerful, because your website and your analysis stay in sync - if the code or data change, you just re‑render.
Below are some examples from the Quarto documentation showing a map and an interactive Plotly plot:
1.2 Non-executable code
If you just want to show code (not run it), you can either:
- Add a dot before the language.
{.python} print("Hello from Python")
- Make the cell as
eval: false.
```{python}
#| eval: false
print("Hello from Python")
```- Set all cells in a Quarto document to
eval: falsein the YAML:
---
title: "Title"
execute:
eval: false
---2 Deploying your site with executable code
As explained on the Hosting, we can deploy a Quarto site using a GitHub Actions workflow, allowing us to host it on GitHub pages.
If your pages contain executable code, the workflow must be amended to installed the required programming language and dependencies.
2.1 Python
2.1.1 requirements.txt
Use this pattern when you have a requirements.txt file listing your Python packages.
This example file is from: https://github.com/pythonhealthdatascience/stars_wp1_summary/.
It is based on the suggested file in the Quarto documentation.
name: Update GitHub pages
run-name: Render Quarto website and publish on GitHub pages
# Source: https://quarto.org/docs/publishing/github-pages.html
on:
push:
branches: main
workflow_dispatch:
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Install python and dependencies
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'
- run: pip install -r requirements.txt
- name: Render and publish to GitHub pages
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}2.1.2 conda
If you are using a Conda environment with an environment.yml/environment.yaml file, you can use this action.
It is taken from: https://github.com/ambmodels/ambdes.
name: Render quarto site and publish on GitHub pages
run-name: Render quarto site and publish on GitHub pages
on:
push:
branches: main
workflow_dispatch:
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Set up Conda environment
uses: conda-incubator/setup-miniconda@v3
with:
environment-file: environment.yaml
activate-environment: ambdes
auto-activate-base: false
channels: conda-forge
- name: Render and publish to GitHub pages
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
path: docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}2.1.3 uv
If you are using uv, you can use a file like below, which is from: https://github.com/lintquarto/lintquarto/.
name: docs
run-name: Render Quarto website and publish on GitHub pages
on:
push:
branches: main
workflow_dispatch:
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Set up Python from .python-version
uses: actions/setup-python@v4
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Sync project from uv.lock
run: uv sync --locked --all-extras --dev
- name: Quarto render
working-directory: docs
run: uv run quarto render
- name: Quarto publish to gh-pages
if: github.ref == 'refs/heads/main'
working-directory: docs
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
uv run quarto publish gh-pages --no-prompt --no-browser2.2 R
If you are using R with renv, you can use an action like below, which is copied from the Quarto documentation.
on:
workflow_dispatch:
push:
branches: main
name: Quarto Publish
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Install R
uses: r-lib/actions/setup-r@v2
with:
r-version: '4.2.0'
- name: Install R Dependencies
uses: r-lib/actions/setup-renv@v2
with:
cache-version: 1
- name: Render and Publish
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}2.3 Python and R
For sites running both Python and R, we found the following approach to work:
- Build a Docker image that contains everything needed.
- Push that image to the GitHub Container Registry (GHCR).
- Run the Quarto render inside the Docker container.
We have used this approach in two of our projects: the DES RAP Book and the Python package vidigi.
2.3.1 Workflow file
name: Publish docker on GHCR and quarto on GitHub pages
run-name: Publish docker on GHCR and quarto on GitHub pages
on:
push:
branches: [main]
workflow_dispatch:
inputs:
force_docker_build:
description: 'Force Docker build (skip change detection)'
required: true
type: boolean
default: false
skip_docker_build:
description: 'Skip Docker build (use last built image)'
required: true
type: boolean
default: false
jobs:
publish-docker:
name: Publish docker
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check if Docker build needed
if: ${{ !inputs.skip_docker_build && !inputs.force_docker_build }}
id: changes
uses: dorny/paths-filter@v3
with:
filters: |
docker:
- 'Dockerfile'
- 'environment.yaml'
- 'renv.lock'
- 'renv/**'
- name: Login to GitHub Container Registry
if: >
(!inputs.skip_docker_build) &&
(inputs.force_docker_build || steps.changes.outputs.docker == 'true')
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build the Docker image
if: >
(!inputs.skip_docker_build) &&
(inputs.force_docker_build || steps.changes.outputs.docker == 'true')
uses: docker/build-push-action@v6
with:
context: .
tags: |
ghcr.io/${{ github.repository_owner }}/des_rap_book:latest
push: true
build-args: |
GIT_PAT=${{ secrets.GITHUB_TOKEN }}
build-deploy:
runs-on: ubuntu-latest
needs: [publish-docker]
container:
image: ghcr.io/${{ github.repository_owner }}/des_rap_book:latest
credentials:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
packages: read
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git safe directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Update YAML date with last commit
run: bash ./update-quarto-date.sh
- name: Render and publish to GitHub pages
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}2.4 Dockerfile
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
# ============================================================
# STAGE 1: Install ALL system dependencies FIRST
# ============================================================
# This must happen before Conda to ensure R compiles against
# system libraries, not conda libraries
RUN set -eux; \
# retry apt-get update a few times in case mirrors are mid-sync
for i in 1 2 3; do \
apt-get update && break; \
echo "apt-get update failed, retrying ($i/3)..."; \
sleep 5; \
done; \
apt-get install -y --no-install-recommends \
# Basic utilities
wget ca-certificates gnupg software-properties-common \
dirmngr locales git \
# R compilation dependencies
build-essential gfortran \
libxml2-dev \
libglpk-dev \
libgmp-dev \
libblas-dev \
liblapack-dev \
libcurl4-openssl-dev \
libssl-dev \
libfontconfig1-dev \
libfreetype6-dev \
libharfbuzz-dev \
libfribidi-dev \
libpng-dev \
libtiff5-dev \
libjpeg-dev \
# Chrome dependencies (for Kaleido/plotly)
fonts-liberation \
libasound2t64 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libatspi2.0-0 \
libcups2 \
libdbus-1-3 \
libgbm1 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libvulkan1 \
libxcomposite1 \
libxdamage1 \
libxkbcommon0 \
libxrandr2 \
xdg-utils && \
locale-gen en_GB.UTF-8 && \
rm -rf /var/lib/apt/lists/*
# Set locale environment variables for Python and other tools
ENV LANG=en_GB.UTF-8
ENV LC_ALL=en_GB.UTF-8
ENV PYTHONIOENCODING=utf-8
# ============================================================
# STAGE 2: Install R from CRAN
# ============================================================
RUN wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | \
gpg --dearmor -o /usr/share/keyrings/r-project.gpg && \
echo "deb [signed-by=/usr/share/keyrings/r-project.gpg] https://cloud.r-project.org/bin/linux/ubuntu noble-cran40/" \
> /etc/apt/sources.list.d/r-project.list && \
apt-get update && \
apt-get install -y --no-install-recommends r-base r-base-dev && \
rm -rf /var/lib/apt/lists/*
# ============================================================
# STAGE 3: Install Quarto
# ============================================================
RUN wget -qO /tmp/quarto.deb https://quarto.org/download/latest/quarto-linux-amd64.deb && \
apt-get update && \
apt-get install -y /tmp/quarto.deb && \
rm /tmp/quarto.deb && \
rm -rf /var/lib/apt/lists/*
# ============================================================
# STAGE 4: Install Google Chrome (required by Kaleido for plotly)
# ============================================================
RUN wget -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get update && \
apt-get install -y --no-install-recommends /tmp/chrome.deb && \
rm /tmp/chrome.deb && \
rm -rf /var/lib/apt/lists/*
# ============================================================
# STAGE 5: Install Miniconda (use explicit paths, not PATH)
# ============================================================
ENV CONDA_DIR=/opt/conda
RUN wget -qO /tmp/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
bash /tmp/miniconda.sh -b -p "$CONDA_DIR" && \
rm /tmp/miniconda.sh
RUN $CONDA_DIR/bin/conda config --system --set always_yes yes && \
$CONDA_DIR/bin/conda config --system --set changeps1 no
# ============================================================
# STAGE 6: Set up project and create conda environment
# ============================================================
WORKDIR /workspace
COPY . /workspace
RUN rm -f /workspace/.Renviron
# Accept Anaconda ToS for required channels (non-interactive)
RUN $CONDA_DIR/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main && \
$CONDA_DIR/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
# Create conda environment using explicit path (NOT in PATH yet)
RUN $CONDA_DIR/bin/conda env create -f environment.yaml
# ============================================================
# STAGE 7: Install R packages WITHOUT conda in PATH
# ============================================================
# CRITICAL: R package compilation must happen with system
# libraries, NOT conda libraries in PATH
ENV RENV_PATHS_LIBRARY=/workspace/renv/library
RUN Rscript -e "install.packages('renv', repos = 'https://cloud.r-project.org')" && \
Rscript -e "renv::restore()"
# ============================================================
# STAGE 8: Activate conda environment for RUNTIME only
# ============================================================
# Now that R packages are installed, it's safe to add conda to PATH
ENV CONDA_DEFAULT_ENV=des-rap-book
ENV PATH="/opt/conda/envs/des-rap-book/bin:${PATH}"
ENV RETICULATE_PYTHON=/opt/conda/envs/des-rap-book/bin/python
RUN echo "conda activate des-rap-book" >> /root/.bashrc
CMD ["/bin/bash"]