A Python CLI and Docker image for running the full molecular-dynamics workflow pipeline (GROMACS + AmberTools + DIALS/cctbx + lunus).
The image is built in stages so the architecture-specific GROMACS compile is isolated from the portable scientific stack — forks targeting other hardware can swap the GROMACS stage without rebuilding the base:
| Stage | Dockerfile | Contents |
|---|---|---|
| base | Dockerfile.base |
CUDA 12.6 devel toolchain, micromamba/conda lunus env, lunus, ChimeraX. Architecture-neutral. |
| gromacs | Dockerfile.gromacs |
GROMACS (CUDA, tuned for H100 / AVX-512 by default) + the md_workflows package. The consumable image. |
(An Astera-specific Dockerfile.actl overlay adds workspace conventions on top of gromacs. It
is tracked here so it stays under CI lint coverage, but is built and published only from the
astera deployment branch.)
From the project root:
# 1. Base — architecture-neutral foundation
docker build -f Dockerfile.base -t md-base:local .
# 2. GROMACS + md-workflows (the runnable image).
# Override the GMX_* build args for non-H100 / non-AVX-512 hardware, e.g.
# --build-arg GMX_CUDA_TARGET_SM=80 --build-arg GMX_SIMD=AVX2_256
docker build -f Dockerfile.gromacs --build-arg BASE_IMAGE=md-base:local -t md-gromacs:local .CI builds these stages and pushes versioned tags (derived from the version in
pyproject.toml) to the Astera Harbor registry; see .github/workflows/build-images.yml on
the astera branch.
Run interactively, mounting the project directory so inputs/outputs are available on the host:
docker run --rm -it \
--user "$(id -u):$(id -g)" \
--gpus all \
--name md_container \
-e HOME=/workspace \
-v "$(pwd):/workspace" \
-w /workspace \
md-gromacs:local \
bashThis registers the single md-workflows CLI entry point from pyproject.toml.
The CLI is one Typer app with a subcommand per step plus run-pipeline. Global options
--workdir/-w (run directory), --config/-c (YAML/TOML), and --resume/--force come
before the subcommand.
Run the full prep pipeline (param_prot → make_crystal → make_waterbox → solvate → minimize → equilibrate → resolvate, ending with the adaptive pressure loop):
md-workflows --workdir . run-pipeline --pdb-id 4LZT --ix 5Run a single step (each resolves its inputs from --workdir and fails loudly if any are
missing):
md-workflows -w . minimize
md-workflows -w . resolvate --target-bar 1.0Per-system settings live in a config file; flags override individual values. The
dominant cross-machine knob is the per-invocation GROMACS mdrun profile
(run_profiles, e.g. GPU offload + thread counts):
# config.yaml
pdb_id: 4LZT
crystal: { ix: 5 }
waterbox: { nc_scale: 5, conc: 60.0 }
solvate: { ionic_strength: 0.1 }
run_profiles:
equil: { ntomp: 16, nb: gpu, pme: gpu, bonded: gpu, tunepme: false }
min: { ntomp: 16, nb: gpu, pme: cpu, bonded: cpu, tunepme: false }md-workflows -w run_dir -c config.yaml run-pipeline
md-workflows -w run_dir -c config.yaml --resume run-pipeline # skip completed steps--resume skips a step whose durable outputs already exist. Exit codes: 0 ok,
1 domain error, 2 missing input, 3 external tool failed.
The same logic is importable:
from md_workflows import run_standard_md
result = run_standard_md("4LZT", "run_dir", crystal={"ix": 5}, resume=True)To see all commands and flags:
md-workflows --help
md-workflows run-pipeline --helpLint and format with ruff (config in pyproject.toml):
pip install '.[dev]'
ruff check md_workflows
ruff format --check md_workflows