fix: include quarto in container

This commit is contained in:
2026-06-29 10:40:33 -04:00
parent e749d9838d
commit bab6b97e3f
2 changed files with 58 additions and 13 deletions
+13 -3
View File
@@ -1,10 +1,20 @@
FROM node:26-trixie-slim@sha256:a1d9d671994fc2d26e297ac56b4b1522a8bc7fa71c43b14cd1b1fe6c5116f7dc FROM node:26-trixie-slim@sha256:a1d9d671994fc2d26e297ac56b4b1522a8bc7fa71c43b14cd1b1fe6c5116f7dc
# OS packages used to fetch sources and deploy output
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
git rsync curl ca-certificates \ git rsync curl ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
ENV PIXI_VERSION=0.71.1 # Quarto setup.
RUN curl -fsSL https://pixi.sh/install.sh | bash ARG QUARTO_VERSION=1.9.38
ENV PATH="/root/.pixi/bin:${PATH}" ARG QUARTO_SHA256=ea8c897368791ad9f200010c087ea3111b2e556b12a960487dd4e216902aa102
RUN curl -fsSL -o /tmp/quarto.tar.gz \
"https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.tar.gz" \
&& echo "${QUARTO_SHA256} /tmp/quarto.tar.gz" | sha256sum -c - \
&& mkdir -p /opt/quarto/${QUARTO_VERSION} \
&& tar -xzf /tmp/quarto.tar.gz -C /opt/quarto/${QUARTO_VERSION} --strip-components=1 \
&& ln -s /opt/quarto/${QUARTO_VERSION}/bin/quarto /usr/local/bin/quarto \
&& rm /tmp/quarto.tar.gz \
&& quarto --version
+45 -10
View File
@@ -6,14 +6,13 @@ The container image our Gitea Actions runners use to build and publish Quarto si
A push to a site repository triggers a runner to render the site and then copy the result to the web server. A push to a site repository triggers a runner to render the site and then copy the result to the web server.
This image is the environment in which that job runs. This image is the environment in which that job runs.
It is deliberately small: rendering uses Quarto's frozen output, so executed R and Python results are committed to each site repository instead of being recomputed here. It is deliberately small: rendering uses Quarto's frozen output, so the executed R and Python results are committed to each site repository rather than recomputed here.
The image carries no language kernels; only what it takes to check out a repository, resolve Quarto, render, and sync files. The image carries no language kernels; only what it takes to check out a repository, render, and sync files.
Node runs the JavaScript actions Gitea uses to bootstrap a job, such as `actions/checkout`. `git` performs the checkout, `rsync` copies the rendered site into place, and `curl` fetches Pixi baked into the image, so each run neither downloads the tool nor pipes a script into a shell. Node runs the JavaScript actions Gitea uses to bootstrap a job, such as `actions/checkout`. `git` performs the checkout, `rsync` copies the rendered site into place, and `curl` fetches Pixi baked into the image, so each run neither downloads the tool nor pipes a script into a shell.
The image stays generic about project dependencies on purpose: it ships Pixi but no packages.
The image stays generic on purpose: it ships the Pixi tool, but no project dependencies, so one image can render every site regardless of which Quarto version that site pins. The Quarto it fixes, baked in and named by the tag, is the renderer the runner uses, and each site mirrors that version in its own `pixi.toml` so local work matches what the runner produces.
Each repository resolves its own dependencies at build time with `pixi install`.
## Build ## Build
@@ -22,7 +21,7 @@ The runners only ever pull it.
You will need an access token with `package:write` access. You will need an access token with `package:write` access.
Go to your Gitea settings and under “Applications,” you will find “Generate New Token.” Go to your Gitea settings and under “Applications,” you will find “Generate New Token.”
Give the token a name like `quarto-deploy-package`, select “Read and Write” for package permissions. Give the token a name like `quarto-deploy-package` and select “Read and Write” for package permissions.
Next, we will authenticate Docker for pushing packages by running `docker login` and using the newly generated token as the password. Next, we will authenticate Docker for pushing packages by running `docker login` and using the newly generated token as the password.
```bash ```bash
@@ -32,16 +31,52 @@ docker login git.scient.ing
Now we can build the package for our servers, which are `linux/amd64`. Now we can build the package for our servers, which are `linux/amd64`.
Note that Apple Silicon defaults to `arm64`, and an `arm64` image may push but then fail to start on the runner with an exec-format error. Note that Apple Silicon defaults to `arm64`, and an `arm64` image may push but then fail to start on the runner with an exec-format error.
`buildx` cross-compiles for the platform you specify, `--push` sends the result straight to the registry, so the build and upload are one step, and the trailing `.` is the build contextthe directory containing this `Dockerfile`. `buildx` cross-compiles for the platform you specify; `--push` sends the result straight to the registry, so the build and upload are one step, and the trailing `.` is the build context (the directory containing this `Dockerfile`).
```bash ```bash
docker buildx build --platform linux/amd64 \ docker buildx build --platform linux/amd64 \
-t git.scient.ing/infra/quarto-deploy:1 --push . -t git.scient.ing/infra/quarto-deploy:1.9.38 --push .
``` ```
Bump the tag (`:2`, `:3`) whenever the `Dockerfile` is changed. Bump the tag whenever the `Dockerfile` changes: to the new Quarto version on a Quarto bump, or with a revision suffix like `:1.9.38-2` otherwise.
The image is always pulled by an explicit version, never `latest`, so a runner's behavior stays tied to a named artifact you can roll back to. The image is always pulled by an explicit version, never `latest`, so a runner's behavior stays tied to a named artifact you can roll back to.
## Quarto versions
Quarto is pinned in the image, not pulled fresh at render time.
The version lives in the `Dockerfile` as the `QUARTO_VERSION` build argument, paired with `QUARTO_SHA256`, the checksum of that release's `linux-amd64` tarball.
The image tag carries the same number, so a runner renders against one known Quarto, and that Quarto travels with the artifact you push, pull, and roll back to.
Each site mirrors this version in its own `pixi.toml` for local work, so a bump happens in two places.
To move to a new Quarto, set `QUARTO_VERSION` in the `Dockerfile` to the release you want and `QUARTO_SHA256` to the matching `quarto-<version>-linux-amd64.tar.gz` checksum from the [releases page](https://github.com/quarto-dev/quarto-cli/releases), then change the `quarto` pin in each site's `pixi.toml` to the same number.
The build verifies the download against the checksum, so a wrong or stale hash fails the build instead of shipping a Quarto you didn't intend.
Then build and push under the new version tag as shown above, point each runner's label at it, and restart the runner.
Site workflows stay untouched. They select the runner by its `quarto-deploy` label, never by version.
## Local development
You author and test a site in a Pixi environment that pins the same Quarto image renders with, so what you see locally is what the runner publishes.
Quarto comes from conda-forge:
```toml
[dependencies]
quarto = { version = "==1.9.38", channel = "conda-forge" }
```
Keep this equal to `QUARTO_VERSION` in the `Dockerfile`.
A few tasks cover the loop:
```toml
[tasks]
serve = { cmd = ["quarto", "preview", "content"] }
build = { cmd = ["quarto", "render", "content", "--output-dir", "../public"] }
bump = { cmd = ["bump-my-version", "bump", "patch"] }
```
`pixi run serve` previews `content/` while you write.
`pixi run build` renders it into `public/`, the directory the runner publishes.
`pixi run bump` advances the site's own version, which is separate from the Quarto pin.
If a site executes code, add the kernels it needs (`jupyter`, `r-base`, and so on) to `pixi.toml`, render once, and commit the resulting `_freeze` so the runner reproduces the output without running anything.
## Use ## Use
@@ -51,7 +86,7 @@ A runner advertises a label that maps to this image, and a workflow selects it w
# in a runner's config.yaml # in a runner's config.yaml
runner: runner:
labels: labels:
- "quarto-deploy:docker://git.scient.ing/infra/quarto-deploy:1" - "quarto-deploy:docker://git.scient.ing/infra/quarto-deploy:1.9.38"
``` ```
```yaml ```yaml