Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git
.github
.codex
.agents
build_*.log
README.md
382 changes: 347 additions & 35 deletions .github/workflows/CI.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logs/
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Agent Guide

## Repository Model

- `docker-bake.hcl` is the build and tag source of truth.
- `images/ubuntu` is the shared base for `exaca` and `thesis`.
- Spack manifests live in `config/spack`; solver lockfiles are generated by CI.
- Published tags are immutable UTC release identifiers. The first daily batch uses
`YYYY-MM-DD`; later batches append `-<suffix>`, progressing from `a` through `z`,
then `aa`. Local builds use `unreleased`.
- A package is a public Bake target with `images/<target>/Dockerfile`. Solver Spack
manifests use the matching `config/spack/<target>.yaml` name; `target:` contexts
define image dependencies.

## Release Rules

- Do not introduce moving release tags such as `main`, `master`, `develop`, or `latest`.
- Keep direct external build inputs immutable in CI: image digests and Git commits.
- Preserve the embedded `/usr/share/ornl-mdf/inventory` artifacts. CI uses them to
generate `docs/containers/` without starting published containers.
- Changing `config/spack/exaca.yaml` or `config/spack/thesis.yaml` must allow CI to
refresh the matching lockfile. Do not hand-edit generated `*.lock` files.
- Do not overwrite a published image release tag or alter historical inventory pages.

## Change Discipline

- Inspect the worktree before editing; it may contain unrelated user changes.
- Use `apply_patch` for manual edits. Do not reset, checkout, or otherwise discard
existing work.
- Keep Dockerfiles compatible with Buildx Bake and the non-root `mdf` runtime user.
- Update `README.md` when public build, tag, or inventory behavior changes.

## Required Checks

Run the focused checks in `TESTING.md` for every change. A full image build needs
registry/network access and is normally validated by GitHub Actions.
182 changes: 175 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,184 @@
# Containers for ORNL-MDF Myna workflows

## Usage
This repository builds Docker images primarily intended for use with Myna workflows and
other workflows that require containerized ORNL-MDF simulation tools.

See available containers at https://github.com/orgs/ORNL-MDF/packages
> [!NOTE]
> Myna itself is expected to be installed in CI or user environments with `uv` or
> other Python package managers, so it does not have a dedicated container here.

Options to install are listed for each package. A local Docker installation is
necessary. Once installed, the container can be started with, e.g.:
## Repository Layout

```text
images/
<container_name>/
Dockerfile
...
config/
spack/
base.yaml
ubuntu.yaml
exaca.yaml
...
docker-bake.hcl
```

Each image has its own directory, while shared Spack environment definitions
live under `config/spack`. `docker-bake.hcl` is the single source of truth for
local builds and CI.

## Image Layout

```text
spack/ubuntu-noble@sha256:<digest> -> ghcr.io/ornl-mdf/containers/ubuntu:<release-tag>
openfoam/openfoam10-paraview510@sha256:<digest> + AdditiveFOAM commit -> ghcr.io/ornl-mdf/containers/additivefoam:<release-tag>
ubuntu target/image -> exaca target
ubuntu target/image -> thesis target
```

`ubuntu:<release-tag>` is the repo-owned Spack base image. It contains the shared
GCC, MPICH, and Kokkos toolchain used by the solver images, together with their
Spack activation. ExaCA and Thesis reuse this installed store and add only their
solver-specific packages. This is intended to be the base image for other
Spack-derived containers; use a different base only when a software environment
has a specific compatibility constraint.

`exaca:<release-tag>` and `thesis:<release-tag>` reuse the `ubuntu` container as their build base.

`additivefoam:<release-tag>` repackages the OpenFOAM Foundation v10 image and layers
AdditiveFOAM 1.2.0 on top of it.

All repo-owned images default to the non-root `mdf` runtime user. Build steps
that need elevated privileges still run as `root` inside the Dockerfile, but
interactive shells and normal container commands start as `mdf`.
They start in `/workspace`, which is owned by `mdf`.

## Building Locally

Build all images and load them into the local Docker daemon:

```sh
mkdir -p logs
for target in ubuntu additivefoam exaca thesis; do
docker buildx bake "$target" 2>&1 | tee "logs/${target}.log"
done
```

When buildign a single target buildx resolves additional context needed to a build a target from
definitions in `docker-bake.hcl`. Run one target and write its log:

```sh
mkdir -p logs
docker buildx bake additivefoam 2>&1 | tee logs/additivefoam.log
```

Override the published namespace or tag:

```sh
mkdir -p logs
for target in ubuntu additivefoam exaca thesis; do
REGISTRY=ghcr.io/ornl-mdf/containers \
docker buildx bake "$target" 2>&1 | tee "logs/${target}.log"
done
```

`docker-bake.hcl` defaults to `type=docker`, so successful local builds are
loaded directly into the local Docker image store.

Published tags are immutable UTC release identifiers. The first daily batch uses
`YYYY-MM-DD`; CI assigns later same-day batches `YYYY-MM-DD-<suffix>`, progressing
from `a` through `z`, then `aa`. The exact installed package versions, base-image
digest, and source revisions for each tag are listed in
[`docs/containers/`](docs/containers/README.md), so no container needs to be started
to inspect its software.

A date-based tag is a completed public release only after it appears in that catalog.
CI first stages verified images in a private GHCR package, then promotes their exact
digests to public tags and commits the catalog and generated Spack locks. A failed
release is resumed only from that same private candidate; CI never rebuilds a
different image under an allocated public tag.
The `ghcr.io/ornl-mdf/containers-staging/<image>` packages must remain private and
grant the repository workflow package write/delete access.

Local builds default to `unreleased`:

```text
ubuntu:unreleased
additivefoam:unreleased
exaca:unreleased
thesis:unreleased
```

After a successful build, smoke test the locally loaded images:

```sh
scripts/container-smoke-tests.sh
```

To smoke test only the solver images, pass their target names:

```sh
scripts/container-smoke-tests.sh exaca thesis
```

The same tests run in CI for every affected image before it can be published.

Override them individually when needed:

```sh
REGISTRY=ghcr.io/ornl-mdf/containers \
RELEASE_TAG="$(date -u +%F)" \
docker buildx bake
```
docker run -it ghcr.io/ornl-mdf/containers/[os]:[version] /bin/bash

The deafult user for the containers is `mdf`. If a debugging session requires
root inside a container, override the runtime user explicitly:

```sh
docker run --user root -it ghcr.io/ornl-mdf/containers/ubuntu:<release-tag> /bin/bash
```

To generate a local-only copy of the container documentation after building
local unreleased images with the default Bake settings, run:

```sh
mkdir -p logs/container-docs

for image in ubuntu additivefoam exaca thesis; do
ref="ghcr.io/ornl-mdf/containers/${image}:unreleased"
digest="$(docker image inspect --format '{{.Id}}' "$ref")"

base_args=()
if [ "$image" = exaca ] || [ "$image" = thesis ]; then
base_args=(--base-image "ghcr.io/ornl-mdf/containers/ubuntu:unreleased")
fi
python3 scripts/generate-container-docs.py \
--image "$image" \
--tag unreleased \
--digest "$digest" \
--output-root logs/container-docs \
"${base_args[@]}"
done
```

ExaCA and Thesis pages link to the matching Ubuntu page for shared package names
and versions; their Installed Software tables list only software added or changed
by the solver image.

## CI Rebuild Policy

CI discovers packages from public Bake targets whose Dockerfile is
`images/<target>/Dockerfile`. It rebuilds the changed target and every target that
depends on it through a Bake `target:` context. `config/spack/<target>.yaml` changes
rebuild that target and refresh its generated lockfile; `config/spack/base.yaml`
rebuilds `ubuntu` and its dependents. Changes to `docker-bake.hcl` or `.dockerignore`
rebuild every discovered package.

## Updating
The Ubuntu Spack manifest is the shared toolchain contract. Changing
`config/spack/ubuntu.yaml` rebuilds `ubuntu`, refreshes its lockfile, and rebuilds
all of its dependent solver images and lockfiles. Changes to the shared
`config/spack/base.yaml` receive the same treatment.

If a new package is needed, add it to the `spack.yaml` and run `./build.sh`
`config/spack/ubuntu.lock` is a build input when present, just like the solver
lockfiles. CI removes it only when the shared environment manifests require a new
concrete solution.
83 changes: 83 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Testing

## Fast Local Checks

Run these after any change:

```sh
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v
docker buildx bake --print
git diff --check
```

The unit test validates Markdown catalog generation from a fixture inventory. The
Bake command validates target inheritance, build arguments, labels, and tags without
pulling or building images. `git diff --check` catches whitespace errors.

## Container Smoke Tests

After a successful local build, run the runtime smoke tests against all locally
loaded `unreleased` images:

```sh
scripts/container-smoke-tests.sh
```

Pass one or more target names to limit the test run, for example:

```sh
scripts/container-smoke-tests.sh exaca thesis
```

The script checks the non-root Ubuntu runtime environment, the AdditiveFOAM
tutorial completion marker, and the serial and MPI error paths of ExaCA and
3DThesis. It expects the solver programs to reject a missing input file; ExaCA
must also print its version banner. CI runs the same script against every
affected image after it builds and before a push can publish it.

## Validate Release Inputs

Use `--print` with representative immutable inputs when modifying Bake variables,
Docker build arguments, or OCI labels:

```sh
RELEASE_TAG=2026-07-10 \
RELEASE_CREATED=2026-07-10T12:00:00Z \
RELEASE_CANDIDATE=candidate-2026-07-10-123-1 \
GIT_REVISION=abc123 \
SPACK_UBUNTU_NOBLE_IMAGE='spack/ubuntu-noble@sha256:c5286e543f226f2c36a6a5ae4c845bc1cd78fad9ece2704dd16256ae774a5d4f' \
OPENFOAM_IMAGE='openfoam/openfoam10-paraview510@sha256:d6ff1f9a2e7bc3c9177f373bebbdeb542fd8b49144afc24d5e3a3cd9bfae253d' \
ADDITIVEFOAM_REF=b8f6d48c53555c303fa8186c895aee5712b6ea02 \
docker buildx bake --print ubuntu additivefoam
```

Confirm the rendered output has a date tag, optionally followed by an alphabetic suffix,
and OCI `source`, `version`, `revision`, `created`, and candidate labels. Do not
substitute a real published date and push from a local machine.

## Change-Specific Coverage

| Change | Minimum validation |
| --- | --- |
| `scripts/` or `docs/` generation | Unit test and inspect generated Markdown fixture output. |
| `docker-bake.hcl` | Bake print with defaults and injected immutable inputs. |
| Dockerfile | Bake print; after a successful local build, run the relevant container smoke tests. CI runs them for every affected image. |
| `config/spack/<target>.yaml` | Verify its Bake target is selected; CI refreshes its lockfile and any dependent lockfiles. |
| `config/spack/base.yaml` or `images/ubuntu/` | Verify `ubuntu` and its Bake dependents are selected; shared-manifest changes also refresh their locks. |
| Workflow | Run the fast checks and review shell quoting, permissions, and push-only steps. |

## CI-Only Validation

GitHub Actions is the release test for registry-dependent behavior. On a `main` push
it uses the pinned external inputs, assigns the next immutable UTC release tag,
preflights embedded inventory before publication, stages candidates in the private
`ghcr.io/ornl-mdf/containers-staging` package, promotes verified digests, and commits
catalog pages and new Spack locks. The catalog commit marks release completion.
Validate the workflow log and resulting `docs/containers/` pages after such a release.
Before the first run, provision `ghcr.io/ornl-mdf/containers-staging/<image>` as a
private package namespace and grant this repository's workflow token package
write/delete access.

If DNS, registry authentication, or external network access is unavailable locally,
do not treat a failed full Docker build as a Dockerfile failure; report the limitation
and run the fast local checks instead.
2 changes: 0 additions & 2 deletions build.sh

This file was deleted.

6 changes: 6 additions & 0 deletions config/spack/base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
concretizer:
unify: true
config:
install_tree:
root: /opt/software
view: /opt/views/view
5 changes: 5 additions & 0 deletions config/spack/exaca.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spack:
include:
- base.yaml
specs:
- exaca@2.0.1 ^mpich@4.3.0 ^kokkos@4.7.04
5 changes: 5 additions & 0 deletions config/spack/thesis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spack:
include:
- base.yaml
specs:
- thesis@4.0.0+mpi ^mpich@4.3.0
6 changes: 6 additions & 0 deletions config/spack/ubuntu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spack:
include:
- base.yaml
specs:
- mpich@4.3.0
- kokkos@4.7.04
34 changes: 34 additions & 0 deletions container_specific_tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
additivefoam
```
# Launch container
docker run -it --rm ghcr.io/ornl-mdf/containers/additivefoam:unreleased

# Check OpenFOAM is on path
which checkMesh # should not be blank

# Check AdditiveFOAM tutorial works
cd /opt/AdditiveFOAM/tutorials/AMB2018-02-B
tail -n 1 log.additiveFoam # should say "Finalising parallel run"
```

exaca
```
# Launch container
docker run -it --rm ghcr.io/ornl-mdf/containers/exaca:unreleased

# Ensure executable is found and launches
which ExaCA
ExaCA # should throw an error saying "must provide path to input file" but should also contain "ExaCA version:" line
mpirun -n 2 ExaCA # should throw input file error, but should also contain "ExaCA version:" line
```

thesis
```
# Launch container
docker run -it --rm ghcr.io/ornl-mdf/containers/thesis:unreleased

# Ensure executable is found and launches
which 3DThesis
3DThesis # should throw an error saying "must provide path to input file"
mpirun -n 2 3DThesis # should throw input file error
```
Loading
Loading