An OCI image for Common Lisp development with Lisp-Stat.
Built from mcr.microsoft.com/devcontainers/base:ubuntu-24.04 using
devcontainer features. The image includes:
- SBCL – Steel Bank Common Lisp, built from source
- Quicklisp – the de-facto package manager for Common Lisp
- ACLREPL – enhanced REPL for SBCL
- Lisp-Stat – statistical computing environment with OpenBLAS
The published image is at:
ghcr.io/lisp-stat/ls-dev:latest
Instead of listing individual features, point your devcontainer.json directly at
this pre-built image to get fast container startup (no feature install phase):
docker run --rm -it --user vscode -w /home/vscode ghcr.io/lisp-stat/ls-dev:latest ls-replThis will give you a Common Lisp REPL with Lisp-Stat loaded. Exiting from the REPL will exit the container.
If you want to edit files with emacs use:
docker run --rm -it --user vscode -w /home/vscode ghcr.io/lisp-stat/ls-dev:latestThe lisp-stat website is available locally. Use this to test your documentation additions or edits. To start it the server:
cd ~/quicklisp/local-projects/documentation
hugo serverThis will start a webserver on the local host at port 1313 that you can connect to. If you're running remotely, you'll probably want to use an SSH tunnel. If running under VS Code, the port forwarding will happen automatically. (If running on VS Code or Code Spaces, the /workspaces directory will be automatically populated with the Lisp-Stat source).
Building requires the devcontainer CLI.
Standard docker build cannot be used because the image is composed from
devcontainer features.
npm install -g @devcontainers/cli
# Build locally for testing
devcontainer build --workspace-folder . --image-name ls-dev:latest
# Build and push to a registry
devcontainer build --workspace-folder . --image-name ghcr.io/lisp-stat/ls-dev:latest --pushThe GitHub Actions workflow in .github/workflows/publish.yml builds and pushes
the image automatically on every push to main and on version tags (v*).
| Tag | Description |
|---|---|
latest |
Most recent build from main |
1.0.0, 1.1.0, … |
Pinned release builds (from v* git tags) |
This image bundles OpenBLAS. An Intel MKL variant can be produced by
creating a second devcontainer.json (e.g. in .devcontainer-mkl/) with
"blas": "intel-mkl" and tagging the resulting image ls-dev:latest-mkl.
The upstream Lisp-Stat repositories are cloned into
~/quicklisp/local-projects/ during the image build. SBCL and Lisp-Stat work
are available no setup required. The repos are read-only copies of the upstream Lisp-Stat organisation. They are
sufficient for using and exploring Lisp-Stat, but you cannot push changes to them.
ls-fork clones your forks into /workspaces/lisp-stat/. That directory must
be backed by persistent storage so your work survives container rebuilds.
After a rebuild, the container automatically re-links ~/quicklisp/local-projects/
to your surviving fork clones — no manual steps required.
Choose the setup that matches your environment before running ls-fork:
/workspaces is automatically a persistent volume in Codespaces — no
configuration needed. Just authenticate and run:
gh auth login
ls-forkAdd a mounts entry to your devcontainer.json to attach persistent storage
to /workspaces/lisp-stat, then rebuild the container before running ls-fork.
Named Docker volume (recommended — no host-side setup):
{
"image": "ghcr.io/lisp-stat/ls-dev:latest",
"mounts": [
"source=lisp-stat-forks,target=/workspaces/lisp-stat,type=volume"
]
}Host bind mount (forks visible in your host filesystem):
{
"image": "ghcr.io/lisp-stat/ls-dev:latest",
"mounts": [
"source=${localWorkspaceFolder}/../lisp-stat,target=/workspaces/lisp-stat,type=bind,consistency=cached"
]
}After adding the mount, Rebuild Container, then:
gh auth login
ls-forkPass a volume flag so /workspaces/lisp-stat persists between container runs:
# Named volume (simplest)
docker run --rm -it --user vscode \
-v lisp-stat-forks:/workspaces/lisp-stat \
ghcr.io/lisp-stat/ls-dev:latest bash
# Or bind to a host directory
docker run --rm -it --user vscode \
-v ~/lisp-stat:/workspaces/lisp-stat \
ghcr.io/lisp-stat/ls-dev:latest bashThen inside the container:
gh auth login
ls-forkAlways pass the same -v flag on subsequent runs to access your fork clones.
For every repo in ~/quicklisp/local-projects/:
- Forks the repo to your GitHub account (via
gh repo fork) - Clones your fork to
/workspaces/lisp-stat/<repo>(on the persistent mount) - Adds an
upstreamremote pointing back to the Lisp-Stat origin - Replaces the in-container
~/quicklisp/local-projects/<repo>directory with a symlink to the fork clone
After running ls-fork:
| Location | Remote | Purpose |
|---|---|---|
/workspaces/lisp-stat/<repo> |
origin → your fork |
Push your changes here |
/workspaces/lisp-stat/<repo> |
upstream → Lisp-Stat |
Fetch official updates |
~/quicklisp/local-projects/<repo> |
symlink → above | SBCL/Quicklisp load path |
ls-fork is idempotent — repos already present in /workspaces/lisp-stat/ are
skipped, so it is safe to re-run if the process is interrupted.
When the container is rebuilt or restarted, the startup scripts automatically
re-link ~/quicklisp/local-projects/<repo> to your fork clones on the
persistent volume. SBCL continues to load your fork code without any manual
intervention.
cd /workspaces/lisp-stat/<repo>
git fetch upstream
git merge upstream/masterOr for all repos at once:
for repo in /workspaces/lisp-stat/*/; do
echo "Updating $(basename $repo)..."
git -C "$repo" fetch upstream
git -C "$repo" merge upstream/master
done- Lisp-Stat
- devcontainer features source
- cl-jupyter-image – companion Jupyter image
MS-PL
{ "name": "My Lisp-Stat Project", "image": "ghcr.io/lisp-stat/ls-dev:latest" }