Quarto Q&A: How to publish your Quarto content as a Docker container?

In this blog post of the “Quarto Q&A” series you will learn how to publish your Quarto project as a Docker container.

Quarto
Q&A
Docker
Author

Mickaël CANOUIL

Published

Sunday, the 7th of May, 2023

A new blog post of the “Quarto Q&A” series.
This time, I will show how to publish your Quarto project as a Docker container as light as possible.

Docker logo with a whale and containers on top of it, and Quarto logo with text inside the whale.

1 The Question/Problem

There are different ways to publish a Quarto project, either if it is a document, a book or a website:

But, what if you want to publish your Quarto project as a Docker container, as an app?

2 The Answer/Solution

Again, there are several ways to do it. I will show you one way to publish your Quarto project as a Docker container, as light as possible. To do so, I will use a Dockerfile with staged builds and a Quarto website project.

2.1 Create a project

First, I create a website using the Quarto CLI and add a code cell to the about.qmd file that uses the palmerpenguins package.
I will publish this Quarto project as a Docker container.

quarto create-project --type website:blog mywebsite
cd mywebsite
echo '```{r}
library(ggplot2)
library(palmerpenguins)
ggplot(penguins) +
  aes(x = bill_length_mm, y = bill_depth_mm) +
  geom_point(aes(colour = species)) +
  geom_smooth(method = "lm", se = FALSE)
```' >> about.qmd

2.2 renv

See https://rstudio.github.io/renv/.

2.2.1 Setup renv

I am using renv to record the dependencies of the project1.

echo 'library(knitr)
library(rmarkdown)
library(ggplot2)
library(palmerpenguins)' >> _dependencies.R
Rscript -e 'install.packages("renv")' -e "renv::init()"
Rscript -e "renv::hydrate()" -e "renv::snapshot()"

2.2.2 The Dockerfile

Here, I am using “build arguments” (i.e., ARGS) to specify the Quarto, the rig and the versions.
This allows to change the versions without having to change the Dockerfile itself.

ARG QUARTO_VERSION="1.3.340"

FROM ghcr.io/quarto-dev/quarto:${QUARTO_VERSION} AS builder

ARG RIG_VERSION="latest"
ARG R_VERSION="release"
COPY install-rig.sh /tmp/install-rig.sh
RUN bash /tmp/install-rig.sh "${RIG_VERSION}"
RUN rig add ${R_VERSION} && Rscript -e 'pak::pkg_install("renv")'

COPY mywebsite /app
WORKDIR /app
RUN Rscript -e "renv::restore()"
RUN quarto render .

FROM httpd:alpine
COPY --from=builder /app/_site/ /usr/local/apache2/htdocs/
1
The base Docker image used as the “builder”, i.e., the image with all requirements to build your Quarto project. Here it is the Quarto image which is an Ubuntu based image with Quarto pre-installed.
The “builder” stage is used to install the specified rig and version, and to render the website.
2
The install-rig.sh script is used to install the rig software.
3
Using rig, we add the specified version and install the renv package.
4
Copy the Quarto project into the /app directory of the “builder”.
5
Restore the lockfile created with pak and render the website with Quarto CLI.
6
The second and last stage is based on the httpd:alpine image which is a light image with Apache pre-installed.
7
Copy the rendered website from the “builder” to the /usr/local/apache2/htdocs/ directory of the second stage image.

2.3 pak

See https://pak.r-lib.org/.

2.3.1 Setup pak

Alternatively to renv, it’s also possible de record (and restore) the dependencies of a project2.

Rscript -e 'install.packages("pak", repos = sprintf("https://r-lib.github.io/p/pak/stable/%s/%s/%s", .Platform$pkgType, R.Version()$os, R.Version()$arch))'
Rscript -e 'pak::lockfile_create(c("knitr", "rmarkdown", "ggplot2", "palmerpenguins"))'

2.3.2 The Dockerfile

Again, I am using “build arguments” (i.e., ARGS) to specify the Quarto, the rig and the versions.
This allows to change the versions without having to change the Dockerfile.

ARG QUARTO_VERSION="1.3.340"

FROM ghcr.io/quarto-dev/quarto:${QUARTO_VERSION} AS builder

ARG RIG_VERSION="latest"
ARG R_VERSION="release"
COPY install-rig.sh /tmp/install-rig.sh
RUN bash /tmp/install-rig.sh "${RIG_VERSION}"
RUN rig add ${R_VERSION}

COPY mywebsite /app
WORKDIR /app
RUN Rscript -e "pak::lockfile_install()" && quarto render .

FROM httpd:alpine
COPY --from=builder /app/_site/ /usr/local/apache2/htdocs/
1
The base Docker image used as the “builder”, i.e., the image with all requirements to build your Quarto project. Here it is the Quarto image which is an Ubuntu based image with Quarto pre-installed.
The “builder” stage is used to install the specified rig and version, and to render the website.
2
The install-rig.sh script is used to install the rig software.
3
Using rig, we add the specified version which already includes pak.
4
Copy the Quarto project into the /app directory of the “builder”.
5
Restore the lockfile created with pak and render the website with Quarto CLI.
6
The second and last stage is based on the httpd:alpine image which is a light image with Apache pre-installed.
7
Copy the rendered website from the “builder” to the /usr/local/apache2/htdocs/ directory of the second stage image.

2.4 Build the image

It’s time to build the image using the Dockerfile and the docker buildx build command.

docker buildx build \
--platform "linux/amd64" \
--build-arg QUARTO_VERSION=1.3.340 \
--tag "mywebsite:1.0.0" \
--push \
.
1
The platform to build the image for.
2
The Quarto version to use when building the image.
3
The tag to use to label the image, i.e., the name mywebsite and the version 1.0.0. This is useful to be able to identify the image later.
4
(Optional) Assuming you have access to a registry (docker login), push the image to the registry, such as Docker Hub.

2.5 Deploy a container

Finally, deploying locally the image as a Docker container using docker container run and access the website from your browser at http://localhost:8080.

docker container run \
   --detach \
   --platform "linux/amd64" \
   --name mywebsite \
   --publish 8080:80 \
   mywebsite:1.0.0
1
The container in detached mode, i.e., the container runs in the background.
2
The platform to run the container on, i.e., same as the platform used to build the image in this case.
3
The name of the container for easy identification, i.e., mywebsite.
4
The port mapping, i.e., we publish the port 8080 of the host to the port 80 of the container. This is useful to be able to access the website from the host.
5
The image to use to run the container, i.e., mywebsite:1.0.0.

Footnotes

  1. See renv documentation for more information on snapshotting dependencies.↩︎

  2. See pak documentation for more information on snapshotting dependencies.↩︎