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
57 changes: 41 additions & 16 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,64 @@ on:
push:
branches:
- main
paths:
- 'Dockerfile.jhub'
- 'jhub-env.lock'
- '.github/workflows/docker.yml'
pull_request:

permissions:
contents: read
actions: write # for type=gha cache writes

env:
IMAGE_NAME: diffuseproject/mdx2-jhub
IMAGE_TAG: 1.0.2-1
# HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 —
# the commit diffuseproject/mdx2:test was originally built from
# (mdx2/VERSION=1.0.2). Reachable only via refs/pull/56/head since the
# branch was deleted post-merge; the Dockerfile fetches that ref
# explicitly. Bump together with IMAGE_TAG when shipping an mdx2 source
# upgrade (e.g. v1.0.4 -> 07f074a, IMAGE_TAG 1.0.2-1 -> 1.0.4-1).
MDX2_COMMIT: 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39

jobs:
docker:
jhub:
name: jhub image (singleuser)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Determine Docker tags
id: tags
- name: Refuse to overwrite an already-published tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "tags=${{ vars.DOCKERHUB_USERNAME }}/mdx2:latest" >> $GITHUB_OUTPUT
else
echo "tags=${{ vars.DOCKERHUB_USERNAME }}/mdx2:test" >> $GITHUB_OUTPUT
if docker manifest inspect "${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}" >/dev/null 2>&1; then
echo "::error::Tag ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} already exists on Dockerhub."
echo "::error::Image tags are immutable. Bump IMAGE_TAG in .github/workflows/docker.yml"
echo "::error::(e.g. 1.0.2-1 -> 1.0.2-2 for stack/lockfile changes; 1.0.3-1 for mdx2 source bump) and merge again."
exit 1
fi
echo "Tag ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} is free to publish."

- name: Build and push
- name: Build (and push on main)
uses: docker/build-push-action@v6
with:
context: .
file: dockerfile
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
file: Dockerfile.jhub
build-args: |
MDX2_COMMIT=${{ env.MDX2_COMMIT }}
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
cache-from: type=gha,scope=jhub
cache-to: type=gha,mode=max,scope=jhub
71 changes: 71 additions & 0 deletions Dockerfile.jhub
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# JupyterHub singleuser image for the Diffuse deployment (notebook pods).
# Reproduces diffuseproject/mdx2:test using a captured conda lockfile
# (jhub-env.lock) for byte-equivalent scientific stack parity with prod,
# plus openssh-client / openssh-sftp-server / rsync so scp/sftp/rsync work
# through the SSH gateway.
#
# Build:
# docker buildx build \
# --build-arg MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 \
# -f Dockerfile.jhub -t diffuseproject/mdx2-jhub:1.0.2-1 .
#
# 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 is the HEAD of feat/jupyterhub-singleuser
# (PR #56 source ref) on 2026-02-25, which is what diffuseproject/mdx2:test was
# built from. mdx2/VERSION=1.0.2. Always use the full 40-char SHA — Docker's
# `git clone` defaults to a shallow clone that may not resolve short SHAs.

FROM mambaorg/micromamba:1.5.5 AS micromamba_stage

# Isolated stage: clone mdx2 at the pinned commit. Keeps git out of the final image.
#
# MDX2_COMMIT is on a deleted branch (`feat/jupyterhub-singleuser`, PR #56) and
# is only reachable via the PR head ref `refs/pull/56/head`. We fetch that ref
# explicitly because default `git clone` doesn't fetch refs/pull/* refs.
# TODO: ask diff-use/mdx2 maintainers to create a durable tag for this commit
# (e.g. `singleuser-source-pin`) so we can drop the PR-ref dependency.
FROM debian:stable-slim AS source_stage
ARG MDX2_COMMIT
ARG MDX2_PR_REF=pull/56/head
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates git \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/diff-use/mdx2.git /tmp/mdx2 \
&& cd /tmp/mdx2 \
&& git fetch origin "${MDX2_PR_REF}:refs/heads/source-pin" \
&& git checkout "${MDX2_COMMIT}" \
&& rm -rf .git

FROM debian:stable-slim AS final

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
openssh-client \
openssh-sftp-server \
rsync \
&& rm -rf /var/lib/apt/lists/*

COPY --from=micromamba_stage /bin/micromamba /usr/local/bin/micromamba

ENV MAMBA_ROOT_PREFIX="/root/micromamba" \
CONDA_PREFIX="/root/micromamba/envs/mdx2-dev" \
PATH="/root/micromamba/envs/mdx2-dev/bin:/usr/local/bin:$PATH"

WORKDIR /home/dev

# Conda env from explicit lockfile — no solve, deterministic, byte-equivalent to live :test pod.
COPY jhub-env.lock /home/dev/jhub-env.lock
RUN --mount=type=cache,target=/root/micromamba/pkgs \
/usr/local/bin/micromamba create -n mdx2-dev --yes --file /home/dev/jhub-env.lock

COPY --from=source_stage /tmp/mdx2/ /home/dev/

RUN --mount=type=cache,target=/root/.cache/pip \
/usr/local/bin/micromamba run -n mdx2-dev pip install -e . \
&& /usr/local/bin/micromamba run -n mdx2-dev pip install \
jupyterhub==5.4.3 \
jupyter-vscode-proxy==0.7

EXPOSE 8888

CMD ["/usr/local/bin/micromamba", "run", "-n", "mdx2-dev", "jupyterhub-singleuser"]
Loading
Loading