devcontainer

Configuration

There are two supported devcontainer configurations for the root project (ref spec): - .devcontainer/dev-build - manually rebuild the container on your machines - deafult - pull CI build image and attach

You can either open the root folder and select one of these two configurations when prompted by VSCode, or open a .devcontainer config from a tutorial/blog post if you want to use the custom environment there.

Codespaces

Since codespaces are naive, they just use the root spec in .devcontainer. Because of this, I have copied the prebuilt config into that directory. This shows up as Main, and should be a safe option for developers.

Dockerfile

For the core docs, I used Python as the base image, and then installed Quarto, TeX, and other dependencies in the Dockerfile:

# syntax=docker/dockerfile:1.3-labs
# ^ Need this for syntax specific to heredoc aliases
# Lets use the latest Python (3.11 was latest at the time)
# https://github.com/devcontainers/images/tree/main/src/python
FROM mcr.microsoft.com/devcontainers/python:3.11-bookworm
ENV QUARTO_VERSION=1.4.533

RUN apt-get update && \
  apt-get install -y --no-install-recommends fontconfig=2.14.1-4 && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/*

# Install Tex Live - this isn't 100% reproducible, but the build will always work and update...
RUN  curl -L -o install-tl-unx.tar.gz https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN zcat < install-tl-unx.tar.gz | tar xf -
RUN rm install-tl-unx.tar.gz
RUN mv "$(find . -maxdepth 1 -type d -regex ".*install-tl.*")" ./install-tlmgr
WORKDIR /install-tlmgr
RUN perl ./install-tl --no-interaction --scheme=small --no-doc-install --no-src-install
# 2024 might break first
ENV PATH=$PATH:/usr/local/texlive/2024/bin/x86_64-linux
WORKDIR /

# Update tlmgr - https://www.tug.org/texlive/tlmgr.html
# Also install any other extensions that are used
#   - This was trial and error as far as I can tell
RUN tlmgr update --self --all && \
  tlmgr install tcolorbox \
  environ \
  tikzfill \
  titlesec \
  xstring \
  pdfcol \
  fontawesome5

# Install pre-commit CLI
RUN pip install --no-cache-dir pre-commit==3.7.0

# Configure aliases with a heredoc
COPY <<-"end_aliases" /home/vscode/.bash_aliases
#!/bin/bash
alias ll="ls -alF"
alias gg="git log --graph --oneline --color"
alias gc="git commit"
alias gck="git checkout"
alias gs="git status"
alias ga="git add"
alias gd="git diff"
alias gf="git fetch --all"
alias gp="git push"
alias grs="git reset --hard"
alias qrp="quarto render && quarto preview"
end_aliases


# Install quarto
RUN curl -L -o quarto-${QUARTO_VERSION}-linux-amd64.tar.gz  https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.tar.gz \
  && tar -xvzf quarto-${QUARTO_VERSION}-linux-amd64.tar.gz \
  && rm quarto-${QUARTO_VERSION}-linux-amd64.tar.gz

# Configure PATH
ENV PATH=$PATH:/quarto-${QUARTO_VERSION}/bin

# When running locally, we really need to expose a port
EXPOSE 8080

Note that TeX is installed here, as well as convenient aliases and other tools like pre-commit.

JSON config

Local Build Config

{
	"build": {
		"dockerfile": "Dockerfile",
		"context": "../.."
	},
	"customizations": {
		"vscode": {
			"extensions": [
				"quarto.quarto",
				"sumneko.lua",
				"ms-azuretools.vscode-docker",
				"github.vscode-github-actions",
				"tomoki1207.pdf",
				"GitHub.copilot"
			]
		}
	},
	"workspaceFolder": "/home/app/",
	"postCreateCommand": ".devcontainer/dev-build/postCreateCommand.sh",
    "workspaceMount": "source=${localWorkspaceFolder},target=/home/app/,type=bind,consistency=cached"
}

CI Image Config

{
	"image": "ghcr.io/cameronrutherford/quarto-ci:latest",
	"customizations": {
		"vscode": {
			"extensions": [
				"quarto.quarto",
				"sumneko.lua",
				"ms-azuretools.vscode-docker",
				"github.vscode-github-actions",
				"tomoki1207.pdf",
				"GitHub.copilot"
			]
		}
	},
	"features": {
		"ghcr.io/devcontainers/features/common-utils:2": {
			"username": "automatic",
			"uid": "automatic",
			"gid": "automatic",
			"installZsh": false,
			"installOhMyZsh": false,
			"upgradePackages": false,
			"nonFreePackages": false
		}
	},
	"workspaceFolder": "/home/app/",
	"postCreateCommand": ".devcontainer/dev-build/postCreateCommand.sh",
    "workspaceMount": "source=${localWorkspaceFolder},target=/home/app/,type=bind,consistency=cached"
}

You can’t quite inherit configurations in devcontainers so this is a little repetitive, but it could be worse.

postCreateCommand.sh

I use a postCreateCommand to run any final configurations inside of the container once it’s running:

#!/bin/bash

# Install username/email for convenience
git config --global --add safe.directory /home/app
git config --global user.email "cameron.rutherford@me.com"
git config --global user.name "cameronrutherford"

# Install pre-commit hooks
cd /home/app
# If poetry is installed, run with that. Otherwise just try running
poetry run pre-commit install --install-hooks || pre-commit install --install-hooks

Since pre-commit was buggy being installed in the quarto-ci base image, we have moved that into the postCreateCommand.sh script, so that every image still gets to share the fix, but none have anything installed by default, making the image smaller! We could also consider removing pre-commit from the base image entirely.

There seems to be a caching issue somewhere, so that file lives in .devcontainer/dev-build. It’s totally possible it can be moved, but I am leving it for now.

The git user is customized to me here for convenience, but you can change this for yourself. As noted in Quickstart, you will want to do git operations outside of the container still.