Skip to content

About

A base image for the UTLN Jupyter

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

solen — Development Container Base

A configurable, profile-driven development container image and tooling built around a modular feature system. solen composes reusable features (small install scripts + metadata) into hierarchical profiles and generates a multi-stage Dockerfile plus a docker-bake.hcl for multi-architecture builds.

Quick links

What Where
GitHub repo ebpro/jupyter-base (the image and CLI are named solen)
Image registry ghcr.io/ebpro/solen
Generated Dockerfile generated/Dockerfile
Bake definition generated/docker-bake.hcl
Profile matrix profiles/matrix/*.yaml → generated/profiles/
Features features/
Version source of truth versions/versions.yaml
Flat build manifest versions.json (generated)
CLI solen-cli/ (pip install -e solen-cli)

Quick start (pull a prebuilt image)

docker pull ghcr.io/ebpro/solen:<profile>-<tag>
docker run --rm -it ghcr.io/ebpro/solen:<profile>-<tag> bash

Tags follow <profile-slug>-<tag> (e.g. data-science-<sha>, minimal-latest, quarto-full-v<version>). On releases the base tag is a multi-arch manifest list; single-arch images are tagged <profile-slug>-<tag>-amd64 / -arm64. List profiles with solen list profiles.

As a devcontainer:

{
  "name": "solen",
  "image": "ghcr.io/ebpro/solen:data-science-latest",
  "workspaceFolder": "/home/jovyan/workspace"
}

generated/devcontainer/ holds per-profile devcontainer.json (and docker-compose.yml where a profile defines services), generated by solen generate devcontainer --all.

Concepts

  • Feature — a folder under features/<name> with feature.json (metadata, options, dependsOn) and an idempotent install.sh. Helpers are prebaked into the image at /opt/solen/_lib (FEATURE_HELPERS_DIR) and resolve versions/checksums centrally.
  • Profile — a named feature set defined in profiles/matrix/*.yaml and expanded by solen generate profiles ... --chain into generated/profiles/<name> (chained profiles inherit their parents).
  • Versions — versions/versions.yaml is the single source of truth (pinned versions
    • upstream sources). solen versions sync flattens it into versions.json, the manifest consumed at build time; solen versions sync --check (CI) fails on drift. Per-arch release checksums live in checksums.json (tools.<name>.checksums.<version>.<arch>).
  • opencode feature — installs the OpenCode AI coding agent (opencode-ai, global npm, version-pinned) and, when SSH_PRIVATE_KEY is supplied at build time, bakes the private ebpro/opencode-config repo into the dev user's ~/.config/opencode/. dependsOn: node. See features/opencode/.

Building locally

python -m venv .venv && . .venv/bin/activate
pip install -e solen-cli

solen generate profiles --matrix profiles/matrix --out generated/profiles --chain
solen generate dockerfile --all --output generated/Dockerfile
solen generate bake --output generated/docker-bake.hcl

# or simply:
./build.sh data-science            # positional profile; flags: --push --load --no-cache --multi-arch

build.sh runs the generation steps and then docker buildx bake final-<profile> (solen build for a single profile). On machines without buildx (e.g. podman-only), fall back to podman build -f generated/Dockerfile --target final-<profile> ..

Optional: pre-download tool binaries (kubectl, helm, gh, quarto, …) into artefacts/ with scripts/prebake-toolcache.sh for faster offline-ish builds.

Artefacts offload pipeline

Large tool archives can be fetched once, verified, and reused across builds:

  • scripts/utils/fetch-artefacts.sh — download + sha256-verify an offloaded tarball into artefacts/ (env: ARTEFACTS_BASE_URL, ARTEFACT_SHA256, or --manifest).
  • scripts/utils/offload-artefacts.sh — tar up artefacts/ + write artefacts/offload/toolcache-manifest-<ts>.json.
  • scripts/validate-artefacts.sh — verify manifests, tarball checksums, and checksums.json validity (run by the Validate Artefacts workflow).

At build time the Dockerfile copies artefacts/ to /opt/solen/artefacts and the feature helpers look for versions.json/checksums.json in the standard search order (/tmp/… during the build stage, /opt/solen/artefacts/… in the final image).

CI

Workflows live in .github/workflows/ and run on the org's in-cluster GitHub Actions runner (actions-runner-controller runner set ebpro-org, with a docker:dind sidecar so docker buildx works as usual):

Workflow Trigger What it does
ci-validate.yml PR / push solen validate features, profile + devcontainer generation, solen versions sync --check, pytest, ruff
ci-build.yml PR / push to develop / dispatch Single-arch build of selected profiles (default quarto-full), Syft SBOM + Trivy scan
ci-publish.yml push to develop / tag v* / dispatch Multi-arch publish to GHCR (5 jobs: native per-arch builds on ebpro-org/ebpro-org-large/ebpro-org-arm, Harbor registry cache, OCI-index merge, cosign sign)
validate-artefacts.yml artefacts/** changes Run scripts/validate-artefacts.sh
cleanup-ghcr.yml schedule GC old GHCR tags

ci-publish.yml runs 5 jobs across 3 in-cluster ARC pools — ebpro-org (standard amd64), ebpro-org-large (heavy/flagship amd64: quarto-full, lang-java-*, quarto-java-*), and ebpro-org-arm (all profiles, arm64). The three build jobs (build-amd64-core, build-amd64, build-arm64) each bake natively on a host of their own architecture (no QEMU/binfmt) against a shared Harbor registry cache (harbor.ebruno.fr/solen/build-cache:<profile>-<arch>, robot robot$arc-runner-ci). On a v* tag or manual dispatch every tag is suffixed per-arch (-amd64/-arm64), then merge-index folds them into a multi-arch manifest list at the base tag and sign-release cosign-signs the merged indexes (release only). On a develop push the fast path is amd64-only with unsuffixed tags.

Contributor guide

  • Add a feature: create features/<name>/feature.json + idempotent install.sh, then validate: solen validate features.
  • Add a profile: edit a profiles/matrix/*.yaml (or add one) and regenerate with solen generate profiles --matrix profiles/matrix --out generated/profiles --chain.
  • Pin a version: edit versions/versions.yaml, run solen versions sync, and update checksums.json via scripts/utils/update-checksums.sh when adding releases.
  • Check for drift: solen versions sync --check, solen validate features, python -m pytest tests/, ruff check solen-cli.

Best practices: keep install.sh small and idempotent; prefer system package managers where sensible; route version lookups through the shared helpers (fh_resolve_version, fh_resolve_checksum) instead of hardcoding.

Appendix: useful commands

solen list profiles                       # available generated profiles
solen inspect-profile data-science        # resolved features/options/services
solen generate devcontainer --all         # per-profile devcontainer.json
solen analyze features --output generated/feature-matrix.md
solen validate propagate-versions         # feature.json options vs versions.yaml
docker buildx bake --file generated/docker-bake.hcl --print

About

A base image for the UTLN Jupyter

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages