Learn how to work with Quarto inside a Docker container so that you can render and serve HTML documents and projects with ease.
Quarto is an open-source scientific and technical publishing system built on Pandoc. It is a cross-platform tool to create dynamic content with Python, R, Julia, and Observable.
Quarto documents follow literate programming principles where the code "chunks" are weaved together with text chunks. From an R programming perspective, Quarto documents with a .qmd
file extension are very similar to R Markdown documents (.Rmd
).
Both .Rmd
and .qmd
files have a YAML header between the triple dashes. The main difference between the two is how the options are specified. Here is a Quarto example:
---
title: "Quarto Demo"
format:
html:
code-fold: true
---
## Air Quality
@fig-airquality further explores the impact of temperature on ozone level.
```{r}
#| label: fig-airquality
#| fig-cap: Temperature and ozone level.
#| warning: false
library(ggplot2)
ggplot(airquality, aes(Temp, Ozone)) +
geom_point() +
geom_smooth(method = "loess"
)
```
In this post, I am going to walk you through how to containerize Quarto documents. You will learn how to install Quarto inside a Docker container and how to use the command line tool to render and eventually serve static Quarto documents.
This post builds on a previous R Markdown-focused article:
Prerequisites
The code from this post can be found in the analythium/quarto-docker-examples GitHub repository:
You will also need Docker Desktop installed.
If you want Quarto to be installed on your local machine, follow these two links to get started: Quarto docs, and RStudio install resources.
Create a Quarto parent image
We build a parent image with Quarto installed so that we can use this image in subsequent FROM
instructions in the Dockerfiles. See the Dockerfile.base
in the repository. The image is based on the `is based on the eddelbuettel/r2u
image using Ubuntu 20.04.
FROM eddelbuettel/r2u:20.04
RUN apt-get update && apt-get install -y --no-install-recommends \
pandoc \
pandoc-citeproc \
curl \
gdebi-core \
&& rm -rf /var/lib/apt/lists/*
RUN install.r \
shiny \
jsonlite \
ggplot2 \
htmltools \
remotes \
renv \
knitr \
rmarkdown \
quarto
RUN curl -LO https://quarto.org/download/latest/quarto-linux-amd64.deb
RUN gdebi --non-interactive quarto-linux-amd64.deb
CMD ["bash"]
The important bits are to have curl
and gdebi
installed so that we can grab the quarto-linux-amd64.deb
file and install the Quarto command line tool. This will install the latest version.
If you want a specific Quarto version (like 0.9.522), use the following lines instead, and change the version to the one you want:
ARG QUARTO_VERSION="0.9.522"
RUN curl -o quarto-linux-amd64.deb -L https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.deb
RUN gdebi --non-interactive quarto-linux-amd64.deb
Now we can build the image:
docker build \
-f Dockerfile.base \
-t analythium/r2u-quarto:20.04 .
We can run the container interactively to check the installation:
docker run -it --rm analythium/r2u-quarto:20.04 bash
Type quarto check
, you should see check marks:
root@d8377016be7f:/# quarto check
[✓] Checking Quarto installation......OK
Version: 1.0.36
Path: /opt/quarto/bin
[✓] Checking basic markdown render....OK
[✓] Checking Python 3 installation....OK
Version: 3.8.10
Path: /usr/bin/python3
Jupyter: (None)
Jupyter is not available in this Python installation.
Install with python3 -m pip install jupyter
[✓] Checking R installation...........OK
Version: 4.2.1
Path: /usr/lib/R
LibPaths:
- /usr/local/lib/R/site-library
- /usr/lib/R/site-library
- /usr/lib/R/library
rmarkdown: 2.14
[✓] Checking Knitr engine render......OK
We are missing Jupyter, but we won't need it for this tutorial. If you want to learn more about the available quarto
commands and options, type quarto help
in the shell to see what is available.
Type exit
to quit the session.
Render a static file
Let's take that air quality example from above that you can find in the static-file/index.qmd
file of the repository. The corresponding Dockerfile.static-file
contains the following:
FROM analythium/r2u-quarto:20.04 AS builder
COPY static-file /app
WORKDIR /app
RUN mkdir output
RUN quarto render index.qmd
RUN rm index.qmd
FROM ghcr.io/openfaas/of-watchdog:0.9.6 AS watchdog
FROM alpine:latest
RUN mkdir /app
COPY --from=builder /app /app
COPY --from=watchdog /fwatchdog .
ENV mode="static"
ENV static_path="/app"
HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1
CMD ["./fwatchdog"]
This pattern is called a multi-stage Docker build, that we have covered in the previous R Markdown post:
- we use the Quarto image to copy the
.qmd
file, then use thequarto render
command to render it to HTML - we pick the
of-watchdog
that is a tiny HTTP server to host our HTML file - the minimal Alpine Linux image is then used to put the
of-watchdog
into and serve the HTML file from the/app
folder through port 8080.
We can build the image and make sure things work as expected by running the image and visiting http://localhost:8080
:
docker build \
-f Dockerfile.static-file \
-t analythium/quarto:static-file .
docker run -p 8080:8080 analythium/quarto:static-file
You should see the rendered document:
Render a static project
Quarto projects are directories that contain multiple files that share the YAML metadata that is placed in the _quarto.yml
file at the root of the project.
Currently, there are two types of projects: websites, and books.
Website
Quarto Websites are a convenient way to publish groups of documents. Documents published as part of a website share navigational elements, rendering options, and visual style.
Use the following command to create a website template locally inside the static-website
directory:
quarto create-project static-website --type website
The command creates the following files:
static-website
├── _quarto.yml
├── about.qmd
├── index.qmd
└── styles.css
The _quarto.yml
file contains the following:
project:
type: website
website:
title: "static-website"
navbar:
left:
- href: index.qmd
text: Home
- about.qmd
format:
html:
theme: cosmo
css: styles.css
toc: true
You have two options to build the Docker image:
- render the HTML locally and copy the rendered files into the image
- copy the project into the image then render the HTML files inside the Docker image
Option 1: local rendering
Render the project as quarto render static-website --output-dir output
into a folder called output
then use this Dockerfile. We use the --output-dir output
option to override the default _book
output directory.
FROM ghcr.io/openfaas/of-watchdog:0.9.6 AS watchdog
FROM alpine:latest
RUN mkdir /app
COPY static-website/output /app
COPY --from=watchdog /fwatchdog .
ENV mode="static"
ENV static_path="/app"
HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1
CMD ["./fwatchdog"]
Option 2: render inside Docker
The Dockerfile for the second option is very similar to the single file rendering example, no need to explain much:
FROM analythium/r2u-quarto:20.04 AS builder
COPY static-website /app
WORKDIR /app
RUN mkdir output
RUN quarto render --output-dir output
FROM ghcr.io/openfaas/of-watchdog:0.9.6 AS watchdog
FROM alpine:latest
RUN mkdir /app
COPY --from=builder /app/output /app
COPY --from=watchdog /fwatchdog .
ENV mode="static"
ENV static_path="/app"
HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1
CMD ["./fwatchdog"]
Build and run the image:
docker build \
-f Dockerfile.static-website \
-t analythium/quarto:static-website .
docker run -p 8080:8080 analythium/quarto:static-website
Go to your browser to see the rendered website template:
Book
Quarto Books are combinations of multiple documents (chapters) into a single manuscript. [...] HTML books are actually just a special type of Quarto Website and consequently support all of the same features as websites including full-text search.
Use the quarto create-project static-book --type book
command to create a book template locally. The template is in the static-book
folder that contains the following files:
static-book
├── _quarto.yml
├── cover.png
├── index.qmd
├── intro.qmd
├── references.bib
├── references.qmd
└── summary.qmd
The _quarto.yml
file now has the following contents:
project:
type: book
book:
title: "static-book"
author: "Jane Doe"
date: "7/25/2022"
chapters:
- index.qmd
- intro.qmd
- summary.qmd
- references.qmd
bibliography: references.bib
format:
html:
theme: cosmo
pdf:
documentclass: scrreprt
Local rendering of the book requires a LaTeX installation on your local machine. If you want to go that way, render the book with quarto render static-book --output-dir output
and modify to previous website example to COPY static-book/output /app
. We use the --output-dir output
option to override the default _book
output directory.
Inside Docker, we can install LaTeX with the quarto install tool tinytex
command in our Dockerfile:
FROM analythium/r2u-quarto:20.04 AS builder
COPY static-book /app
WORKDIR /app
RUN mkdir output
RUN quarto install tool tinytex
RUN quarto render --output-dir output
FROM ghcr.io/openfaas/of-watchdog:0.9.6 AS watchdog
FROM alpine:latest
RUN mkdir /app
COPY --from=builder /app/output /app
COPY --from=watchdog /fwatchdog .
ENV mode="static"
ENV static_path="/app"
HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1
CMD ["./fwatchdog"]
Build and run the image:
docker build \
-f Dockerfile.static-book \
-t analythium/quarto:static-book .
docker run -p 8080:8080 analythium/quarto:static-book
Visit http://localhost:8080
to see the rendered HTML version of the book:
Render an interactive file with widgets
Static files can be interactive via incorporating JavaScript into the HTML, like interactive Plotly or Echarts graphs, or Leaflet maps.
From R, we can do this with the htmlwidget R package. Here is the static-widget/index.qmd
file's contents:
---
title: "Interactivity with HTML Widgets"
format:
html:
code-fold: true
---
## Leaflet
Example following [this](https://quarto.org/docs/interactive/widgets/htmlwidgets.html) page.
```{r}
library(leaflet)
leaflet() %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")
```
## Layout
```{r}
#| layout: [[1,1], [1]]
library(dygraphs)
dygraph(fdeaths, "Female Deaths")
dygraph(mdeaths, "Male Deaths")
dygraph(ldeaths, "All Deaths")
```
The corresponding Dockerfile.static-widget
file has a few lines to install a few packages: leaflet and dygraphs.
FROM analythium/r2u-quarto:20.04 AS builder
RUN install.r \
dygraphs \
leaflet
COPY static-widget /app
WORKDIR /app
RUN mkdir output
RUN quarto render index.qmd
RUN rm index.qmd
FROM ghcr.io/openfaas/of-watchdog:0.9.6 AS watchdog
FROM alpine:latest
RUN mkdir /app
COPY --from=builder /app /app
COPY --from=watchdog /fwatchdog .
ENV mode="static"
ENV static_path="/app"
HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1
CMD ["./fwatchdog"]
Finally, build and run the image:
docker build \
-f Dockerfile.static-widget \
-t analythium/quarto:static-widget .
docker run -p 8080:8080 analythium/quarto:static-widget
Open the rendered HTML in your browser to check the interactive parts:
Conclusions
We reviewed how to install Quarto inside Docker image, so that we can render static HTML documents and projects, including JavaScript-based interactivity.
You are now able to serve files, websites, and books from Docker containers, which gives you many self-hosting options beyond the publishing options mentioned in the official Quarto docs.