diff --git a/Dockerfile b/Dockerfile index cbbad0d..831b471 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,20 @@ FROM node:26-trixie-slim@sha256:a1d9d671994fc2d26e297ac56b4b1522a8bc7fa71c43b14cd1b1fe6c5116f7dc +# OS packages used to fetch sources and deploy output RUN apt-get update \ && apt-get install -y --no-install-recommends \ git rsync curl ca-certificates \ && rm -rf /var/lib/apt/lists/* -ENV PIXI_VERSION=0.71.1 -RUN curl -fsSL https://pixi.sh/install.sh | bash -ENV PATH="/root/.pixi/bin:${PATH}" +# Quarto setup. +ARG QUARTO_VERSION=1.9.38 +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 diff --git a/README.md b/README.md index a18efee..86a798c 100644 --- a/README.md +++ b/README.md @@ -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. 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. -The image carries no language kernels; only what it takes to check out a repository, resolve Quarto, render, and sync files. +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, 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. - -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. -Each repository resolves its own dependencies at build time with `pixi install`. +The image stays generic about project dependencies on purpose: it ships Pixi but no packages. +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. ## Build @@ -22,7 +21,7 @@ The runners only ever pull it. You will need an access token with `package:write` access. 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. ```bash @@ -32,16 +31,52 @@ docker login git.scient.ing 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. -`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`. +`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 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. +## 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--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 @@ -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 runner: labels: - - "quarto-deploy:docker://git.scient.ing/infra/quarto-deploy:1" + - "quarto-deploy:docker://git.scient.ing/infra/quarto-deploy:1.9.38" ``` ```yaml