Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .actlignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Large trajectory outputs should live on /mnt/diffuse-shared (or the pod home
# PVC), not in the synced source checkout. Keep source-controlled templates such
# as artifacts/*.mdp synced.
*.cpt
*.dcd
*.edr
*.trr
*.xtc
163 changes: 163 additions & 0 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: Build & push md-workflows images

# Builds the three-stage image chain on the self-hosted Astera builder and pushes to Harbor.
#
# Security: this workflow has NO pull_request trigger, so fork PRs can never reach the
# self-hosted runner or the Harbor credentials. Heavy builds run only on push to `astera`
# (path-filtered) and manual dispatch. Credentials live in the `harbor` GitHub Environment,
# which must be restricted to the `astera` deployment branch; the `astera-sh-builder` runner
# should sit in a runner group scoped to this repo. See the runbook in the PR description.
on:
push:
branches: [astera]
paths:
- Dockerfile.base
- Dockerfile.gromacs
- Dockerfile.actl
- pyproject.toml
- md_workflows/**
- .github/workflows/build-images.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: build-images-${{ github.ref }}
cancel-in-progress: true

env:
REGISTRY: harbor.astera.sh
# base + gromacs (bake the non-redistributable ChimeraX) live in the proprietary project,
# distinguished by a stage tag prefix. The final consumable actl image lands in library.
PROPRIETARY_IMAGE: harbor.astera.sh/diffuse-proprietary/md-workflows
LIBRARY_IMAGE: harbor.astera.sh/library/md-workflows

jobs:
version:
name: Compute version
runs-on: ubuntu-latest
outputs:
semver: ${{ steps.v.outputs.semver }}
build: ${{ steps.v.outputs.build }}
sha: ${{ steps.v.outputs.sha }}
steps:
- uses: actions/checkout@v4
- id: v
name: Derive version from pyproject.toml
run: |
set -euo pipefail
SEMVER=$(grep -m1 -E '^version[[:space:]]*=' pyproject.toml | sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "${SEMVER}" ]; then echo "could not parse version from pyproject.toml" >&2; exit 1; fi
SHORT="${GITHUB_SHA::7}"
echo "semver=${SEMVER}" >> "$GITHUB_OUTPUT"
echo "build=${SEMVER}-b${{ github.run_number }}" >> "$GITHUB_OUTPUT"
echo "sha=sha-${SHORT}" >> "$GITHUB_OUTPUT"
echo "Resolved: ${SEMVER} / ${SEMVER}-b${{ github.run_number }} / sha-${SHORT}"

base:
name: Build base
needs: version
runs-on: [self-hosted, diffuse-sh-builder]
environment: harbor
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- id: build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.base
platforms: linux/amd64
push: true
provenance: false
tags: |
${{ env.PROPRIETARY_IMAGE }}:base-${{ needs.version.outputs.semver }}
${{ env.PROPRIETARY_IMAGE }}:base-${{ needs.version.outputs.build }}
${{ env.PROPRIETARY_IMAGE }}:base-${{ needs.version.outputs.sha }}
${{ env.PROPRIETARY_IMAGE }}:base
cache-from: type=registry,ref=${{ env.PROPRIETARY_IMAGE }}:base-buildcache
cache-to: type=registry,ref=${{ env.PROPRIETARY_IMAGE }}:base-buildcache,mode=max

gromacs:
name: Build gromacs
needs: [version, base]
runs-on: [self-hosted, diffuse-sh-builder]
environment: harbor
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- id: build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.gromacs
platforms: linux/amd64
push: true
provenance: false
build-args: |
BASE_IMAGE=${{ env.PROPRIETARY_IMAGE }}@${{ needs.base.outputs.digest }}
tags: |
${{ env.PROPRIETARY_IMAGE }}:gromacs-${{ needs.version.outputs.semver }}
${{ env.PROPRIETARY_IMAGE }}:gromacs-${{ needs.version.outputs.build }}
${{ env.PROPRIETARY_IMAGE }}:gromacs-${{ needs.version.outputs.sha }}
${{ env.PROPRIETARY_IMAGE }}:gromacs
cache-from: type=registry,ref=${{ env.PROPRIETARY_IMAGE }}:gromacs-buildcache
cache-to: type=registry,ref=${{ env.PROPRIETARY_IMAGE }}:gromacs-buildcache,mode=max

actl:
name: Build actl (consumable)
needs: [version, gromacs]
runs-on: [self-hosted, diffuse-sh-builder]
environment: harbor
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- id: build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.actl
platforms: linux/amd64
push: true
provenance: false
build-args: |
GROMACS_IMAGE=${{ env.PROPRIETARY_IMAGE }}@${{ needs.gromacs.outputs.digest }}
tags: |
${{ env.LIBRARY_IMAGE }}:${{ needs.version.outputs.semver }}
${{ env.LIBRARY_IMAGE }}:${{ needs.version.outputs.build }}
${{ env.LIBRARY_IMAGE }}:${{ needs.version.outputs.sha }}
${{ env.LIBRARY_IMAGE }}:actl
${{ env.LIBRARY_IMAGE }}:latest
cache-from: type=registry,ref=${{ env.LIBRARY_IMAGE }}:actl-buildcache
cache-to: type=registry,ref=${{ env.LIBRARY_IMAGE }}:actl-buildcache,mode=max
- name: Report digests
run: |
echo "base digest: ${{ needs.base.outputs.digest }}" || true
echo "gromacs digest: ${{ needs.gromacs.outputs.digest }}"
echo "actl digest: ${{ steps.build.outputs.digest }}"
echo "Published ${{ env.LIBRARY_IMAGE }}:${{ needs.version.outputs.build }}"
92 changes: 92 additions & 0 deletions Dockerfile.actl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# syntax=docker/dockerfile:1
# ==================== md-workflows Astera ACTL overlay (stage 3 of 3) ====================
# Layers Astera workspace conventions on top of the GROMACS stage (Dockerfile.gromacs): the
# /home/dev persisted home, editor/sync/debug tools, root-oriented interactive pods, and the
# global shell init that keeps the baked lunus env active. The scientific stack (CUDA 12.6,
# GROMACS sm_90 + AVX_512, md-workflows) is inherited unchanged from the gromacs image.
#
# Build locally (after building md-gromacs:local):
# docker buildx build --platform linux/amd64 \
# -f Dockerfile.actl --build-arg GROMACS_IMAGE=md-gromacs:local \
# -t harbor.astera.sh/library/md-workflows:local-actl .
ARG GROMACS_IMAGE=md-gromacs:local
FROM ${GROMACS_IMAGE} AS actl

USER root

ARG ACTL_PACKAGES="bash ca-certificates curl wget rsync tini vim nano emacs-nox git zsh htop tmux ncdu iputils-ping dnsutils"

ENV DEBIAN_FRONTEND=noninteractive \
HOME=/home/dev \
XDG_CONFIG_HOME=/home/dev/.config \
XDG_CACHE_HOME=/home/dev/.cache \
XDG_DATA_HOME=/home/dev/.local/share \
SHELL=/bin/bash \
MAMBA_ROOT_PREFIX=/opt/micromamba \
MAMBA_EXE=/opt/micromamba/bin/micromamba \
CONDA_PREFIX=/opt/micromamba/envs/lunus \
PYTHONPATH=/home/dev/workspace:/opt/md-workflows \
PATH="/opt/micromamba/bin:/opt/micromamba/envs/lunus/bin:/usr/local/gromacs/bin:${PATH}"

RUN apt-get update && apt-get install -y --no-install-recommends \
${ACTL_PACKAGES} \
bc \
bzip2 \
coreutils \
libgomp1 \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& mkdir -p /home/dev/.config /home/dev/.cache /home/dev/.local/share /home/dev/workspace /etc/zsh \
&& cat > /usr/local/share/actl-md-workflows-shell-init.sh <<'EOF'
# Keep the baked md-workflows/lunus environment active while letting ACTL's
# synced checkout at /home/dev/workspace override the baked package for edits.
if [ -d /opt/micromamba/envs/lunus/bin ]; then
case ":${PATH}:" in
*:/opt/micromamba/envs/lunus/bin:*) ;;
*) PATH="/opt/micromamba/envs/lunus/bin:${PATH}" ;;
esac
export PATH
CONDA_PREFIX=/opt/micromamba/envs/lunus
export CONDA_PREFIX
fi

if [ -x /opt/micromamba/bin/micromamba ]; then
case "${ZSH_VERSION:+zsh}${BASH_VERSION:+bash}" in
zsh*) eval "$(/opt/micromamba/bin/micromamba shell hook --shell zsh 2>/dev/null)" || true ;;
*bash*) eval "$(/opt/micromamba/bin/micromamba shell hook --shell bash 2>/dev/null)" || true ;;
esac
fi

if [ -d /home/dev/workspace ]; then
case ":${PYTHONPATH:-}:" in
*:/home/dev/workspace:*) ;;
*) PYTHONPATH="/home/dev/workspace${PYTHONPATH:+:${PYTHONPATH}}" ;;
esac
fi
if [ -d /opt/md-workflows ]; then
case ":${PYTHONPATH:-}:" in
*:/opt/md-workflows:*) ;;
*) PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}/opt/md-workflows" ;;
esac
fi
export PYTHONPATH

case "$-" in
*i*)
if [ -d /home/dev/workspace ]; then
cd /home/dev/workspace || true
fi
;;
esac
EOF
RUN chmod 0644 /usr/local/share/actl-md-workflows-shell-init.sh \
&& printf '\n# Astera md-workflows environment\n[ -r /usr/local/share/actl-md-workflows-shell-init.sh ] && . /usr/local/share/actl-md-workflows-shell-init.sh\n' >> /etc/bash.bashrc \
&& printf '\n# Astera md-workflows environment\n[ -r /usr/local/share/actl-md-workflows-shell-init.sh ] && . /usr/local/share/actl-md-workflows-shell-init.sh\n' >> /etc/zsh/zshrc \
&& for cmd in \
md_workflows.mdmx gmx micromamba curl wget rsync tini vim nano emacs git zsh htop tmux ncdu ping dig; do \
command -v "${cmd}" >/dev/null; \
done

WORKDIR /home/dev
SHELL ["/bin/bash", "-c"]
CMD ["bash"]
Loading