From 9f0ff5df4fe851b78b4b3a72d8b11000ec5fb9a6 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 21 Apr 2026 13:23:32 +0200 Subject: [PATCH 01/12] move deeplabcut-docker package to separate subdirectory --- docker/{ => package}/LICENSE | 0 docker/{ => package}/MANIFEST.in | 0 docker/{ => package}/Makefile | 2 +- docker/{ => package}/deeplabcut_docker.py | 0 docker/{ => package}/deeplabcut_docker.sh | 0 docker/{ => package}/pyproject.toml | 0 docker/{ => package}/setup.cfg | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename docker/{ => package}/LICENSE (100%) rename docker/{ => package}/MANIFEST.in (100%) rename docker/{ => package}/Makefile (87%) rename docker/{ => package}/deeplabcut_docker.py (100%) rename docker/{ => package}/deeplabcut_docker.sh (100%) rename docker/{ => package}/pyproject.toml (100%) rename docker/{ => package}/setup.cfg (100%) diff --git a/docker/LICENSE b/docker/package/LICENSE similarity index 100% rename from docker/LICENSE rename to docker/package/LICENSE diff --git a/docker/MANIFEST.in b/docker/package/MANIFEST.in similarity index 100% rename from docker/MANIFEST.in rename to docker/package/MANIFEST.in diff --git a/docker/Makefile b/docker/package/Makefile similarity index 87% rename from docker/Makefile rename to docker/package/Makefile index 01439891bc..570d996440 100644 --- a/docker/Makefile +++ b/docker/package/Makefile @@ -4,7 +4,7 @@ clean: prepare_build: python3 -m pip install --upgrade twine build - cp ../docs/docker.md PYPI_README.md + cp ../../docs/docker.md PYPI_README.md build: clean prepare_build python3 -m build diff --git a/docker/deeplabcut_docker.py b/docker/package/deeplabcut_docker.py similarity index 100% rename from docker/deeplabcut_docker.py rename to docker/package/deeplabcut_docker.py diff --git a/docker/deeplabcut_docker.sh b/docker/package/deeplabcut_docker.sh similarity index 100% rename from docker/deeplabcut_docker.sh rename to docker/package/deeplabcut_docker.sh diff --git a/docker/pyproject.toml b/docker/package/pyproject.toml similarity index 100% rename from docker/pyproject.toml rename to docker/package/pyproject.toml diff --git a/docker/setup.cfg b/docker/package/setup.cfg similarity index 100% rename from docker/setup.cfg rename to docker/package/setup.cfg From 26ea5b615f364ceef4d6032d1393c46edea61c00 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 21 Apr 2026 13:54:33 +0200 Subject: [PATCH 02/12] update dockerfile: add single clean dockerfile that replaces the multiple dockerfiles --- docker/Dockerfile | 51 +++++++++++++++++++++++++++++++++++++++ docker/Dockerfile.base | 32 ------------------------ docker/Dockerfile.core | 9 ------- docker/Dockerfile.jupyter | 9 ------- docker/Dockerfile.test | 15 ------------ 5 files changed, 51 insertions(+), 65 deletions(-) create mode 100644 docker/Dockerfile delete mode 100644 docker/Dockerfile.base delete mode 100644 docker/Dockerfile.core delete mode 100644 docker/Dockerfile.jupyter delete mode 100644 docker/Dockerfile.test diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..d26b9917d9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,51 @@ +# syntax=docker/dockerfile:1 + +ARG PYTORCH_VERSION=2.5.1 +ARG CUDA_VERSION=12.4 +ARG CUDNN_VERSION=9 +ARG DEEPLABCUT_VERSION=3.0.0rc14 + +# ── core ────────────────────────────────────────────────────────────────────── +FROM pytorch/pytorch:${PYTORCH_VERSION}-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION}-runtime AS core + +ARG DEEPLABCUT_VERSION +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -yy && \ + apt-get install -yy --no-install-recommends \ + libgl1 \ + libglib2.0-0 \ + build-essential \ + git \ + ffmpeg \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --upgrade pip && \ + pip install "deeplabcut[modelzoo,wandb]==${DEEPLABCUT_VERSION}" + +RUN chmod a+rwx -R \ + /opt/conda/lib/python3.11/site-packages/deeplabcut/pose_estimation_pytorch/models/backbones/pretrained_weights + +RUN mkdir -p /app && chmod a+rwx /app + +COPY motd.sh /home/motd.sh +RUN echo "source /home/motd.sh" >> /etc/profile + +ENV CUDA_VERSION=${CUDA_VERSION} +ENV PYTORCH_VERSION=${PYTORCH_VERSION} +ENV DEEPLABCUT_VERSION=${DEEPLABCUT_VERSION} + +WORKDIR /app + +# ── jupyter ─────────────────────────────────────────────────────────────────── +FROM core AS jupyter + +RUN pip install notebook +EXPOSE 8888 +ENTRYPOINT ["jupyter", "notebook", \ + "--no-browser", "--NotebookApp.token=deeplabcut", "--ip", "0.0.0.0"] + +# ── test (CI only, not published to Hub) ───────────────────────────────────── +FROM core AS test + +RUN pip install --no-cache-dir pytest diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base deleted file mode 100644 index 856be0a985..0000000000 --- a/docker/Dockerfile.base +++ /dev/null @@ -1,32 +0,0 @@ -ARG CUDA_VERSION -ARG CUDNN_VERSION -ARG PYTORCH_VERSION -FROM pytorch/pytorch:${PYTORCH_VERSION}-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION}-runtime - -ARG DEEPLABCUT_VERSION -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update -yy && \ - apt-get install -yy --no-install-recommends \ - libgl1 \ - libglib2.0-0 \ - build-essential \ - make \ - git \ - ffmpeg \ - && rm -rf /var/lib/apt/lists/* \ - && apt-get clean - -# FIXME(maxim) install DeepLabCut through pypi releases: `pip install deeplabcut==${DEEPLABCUT_VERSION}` -RUN pip install --upgrade pip -RUN python -m pip install git+https://github.com/DeepLabCut/DeepLabCut.git \ - huggingface_hub \ - wandb - -# Make the folder containing the pretrained weights writeable -RUN mkdir -p /opt/conda/lib/python3.11/site-packages/deeplabcut/pose_estimation_pytorch/models/backbones/pretrained_weights && \ - chmod a+rwx -R /opt/conda/lib/python3.11/site-packages/deeplabcut/pose_estimation_pytorch/models/backbones/pretrained_weights - -ENV CUDA_VERSION=${CUDA_VERSION} -ENV DEEPLABCUT_VERSION=${DEEPLABCUT_VERSION} -ENV PYTORCH_VERSION=${CUDA_VERSION} diff --git a/docker/Dockerfile.core b/docker/Dockerfile.core deleted file mode 100644 index ba5d8d3f50..0000000000 --- a/docker/Dockerfile.core +++ /dev/null @@ -1,9 +0,0 @@ -ARG CUDA_VERSION -ARG CUDNN_VERSION -ARG DEEPLABCUT_VERSION -FROM deeplabcut/deeplabcut:${DEEPLABCUT_VERSION}-base-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION} - -ENV DLClight True - -COPY motd.sh /home/motd.sh -RUN echo "source /home/motd.sh" >> /etc/profile diff --git a/docker/Dockerfile.jupyter b/docker/Dockerfile.jupyter deleted file mode 100644 index f88d7a5d00..0000000000 --- a/docker/Dockerfile.jupyter +++ /dev/null @@ -1,9 +0,0 @@ -ARG CUDA_VERSION -ARG CUDNN_VERSION -ARG DEEPLABCUT_VERSION -FROM deeplabcut/deeplabcut:${DEEPLABCUT_VERSION}-core-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION} - -RUN pip install "notebook<7" - -EXPOSE 8888 -ENTRYPOINT ["jupyter", "notebook", "--no-browser", "--NotebookApp.token=deeplabcut", "--ip", "0.0.0.0"] diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test deleted file mode 100644 index d7fd6ab24d..0000000000 --- a/docker/Dockerfile.test +++ /dev/null @@ -1,15 +0,0 @@ -ARG CUDA_VERSION -ARG CUDNN_VERSION -ARG DEEPLABCUT_VERSION -FROM deeplabcut/deeplabcut:${DEEPLABCUT_VERSION}-core-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION} - -RUN mkdir test/ -WORKDIR test -RUN apt-get update && apt-get install -yy git -RUN git config --global advice.detachedHead false -# FIXME(maxim): pull from --branch v${DEEPLABCUT_VERSION} -RUN git clone --depth 1 --branch main \ - https://github.com/DeepLabCut/DeepLabCut.git /test - -RUN pip3 install --no-cache-dir pytest -RUN chmod a+rwx -R /test From a80e426406b70f19f6d542cacafc595deda25f0d Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 21 Apr 2026 14:17:55 +0200 Subject: [PATCH 03/12] docker: replace build script with docker bake buildx hcl file --- docker/build.sh | 180 ----------------------------------------- docker/docker-bake.hcl | 47 +++++++++++ 2 files changed, 47 insertions(+), 180 deletions(-) delete mode 100755 docker/build.sh create mode 100644 docker/docker-bake.hcl diff --git a/docker/build.sh b/docker/build.sh deleted file mode 100755 index 506c688624..0000000000 --- a/docker/build.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash -# Build script for deeplabcut docker images. -# > docker/build.sh [build|test|push] - -set -e - -# Set default Docker binary -export DOCKER=${DOCKER:-'docker'} -export DOCKER_BUILD="$DOCKER build" -export BASENAME=deeplabcut/deeplabcut -export DOCKERDIR=docker - -# Check if script is being run from the correct directory -if [[ ! -d ./${DOCKERDIR} ]]; then - echo >&2 "Run from root of the DeepLabCut repository (i.e. run docker/build.sh). Current pwd is" - pwd >&2 - exit 1 -fi - -# List Docker images related to DeepLabCut -list_images() { - $DOCKER images | - grep '^deeplabcut ' | - sed -s 's/\s\s\+/\t/g' | - cut -f1,2 -d$'\t' --output-delimiter ':' | - grep core -} - -# Run tests inside Docker containers -run_test() { - kwargs=( - -u $(id -u) --tmpfs /.local --tmpfs /.cache - --tmpfs /test/.pytest_cache - --env DLClight=True -t - $1 - ) - - # Unit tests - $DOCKER run ${kwargs[@]} python3 -m pytest -v tests || return 255 - - # Functional tests - $DOCKER run ${kwargs[@]} python3 testscript_cli.py || return 255 - - return 0 -} -export -f run_test - -# Iterate through build matrix and perform actions -iterate_build_matrix() { - # Define latest DLC version - latest_dlc_version="3.0.0" - - # [add other dlc versions to build here] - dlc_versions=( - "${latest_dlc_version}" - ) - - # [add other cuda versions to build here] - cuda_versions=( - "11.8" - "12.1" - "12.4" - ) - cudnn_version="9" - - pytorch_versions=( - "2.5.1" - ) - - docker_types=( - "base" - "core" - "jupyter" - "test" - ) - - mode=${1:-build} - for cuda_version in \ - ${cuda_versions[@]}; do - for deeplabcut_version in \ - ${dlc_versions[@]}; do - for pytorch_version in \ - ${pytorch_versions[@]}; do - for stage in \ - ${docker_types[@]}; do - tag_suffix="${stage}-cuda${cuda_version}-cudnn${cudnn_version}" - version_tag="${deeplabcut_version}-${tag_suffix}" - latest_tag="latest-${tag_suffix}" - case "$mode" in - build) - build_args="--build-arg=CUDA_VERSION=${cuda_version} \ - --build-arg=CUDNN_VERSION=${cudnn_version} \ - --build-arg=DEEPLABCUT_VERSION=${deeplabcut_version} \ - --build-arg=PYTORCH_VERSION=${pytorch_version}" - - if [ "${deeplabcut_version}" = "${latest_dlc_version}" ]; then - echo "${build_args} \ - --tag=${BASENAME}:${version_tag} \ - --tag=${BASENAME}:${latest_tag} \ - -f Dockerfile.${stage} \." - # --no-cache \ - else - echo "${build_args} \ - --tag=${BASENAME}:${version_tag} \ - -f Dockerfile.${stage} \." - # --no-cache \ - fi - ;; - push | clean | test) - echo "${BASENAME}:${version_tag}" - if [ "${deeplabcut_version}" = "${latest_dlc_version}" ]; then - echo "${BASENAME}:${latest_tag}" - fi - ;; - esac - done - done - done - done -} - -# Get Git hash -githash() { - git log -1 --pretty=format:"%h" -} - -# Create logs directory and set log file name -mkdir -p logs -logfile=logs/$(date +%y%m%d-%H%M%S)-$(githash) -echo "Logging to $logdir.*" - -if [ $# -eq 0 ]; then - echo "Help: Provide arguments to this script." - echo "Usage: $0 [build|test|push]" - exit 1 -fi - -# Iterate through command line arguments -for arg in "$@"; do - case $1 in - clean) - iterate_build_matrix clean | - tr '\n' '\0' | - xargs -I@ -0 bash -c "docker image rm @ |& grep -v 'No such image'" - ;; - build) - echo "DeepLabCut docker build:: $(git log -1 --oneline)" - cp -r examples ${DOCKERDIR} - ( - cd ${DOCKERDIR} - iterate_build_matrix | - tr '\n' '\0' | - xargs -I@ -0 bash -c "echo Building @; $DOCKER build @ || exit 255" - echo Successful build. - ) |& tee ${logfile}.build - ;; - test) - ( - echo "DeepLabCut docker build:: $(git log -1 --oneline)" - iterate_build_matrix test | - grep '\-test\-' | - tr '\n' '\0' | - xargs -0 -I@ bash -c "run_test @ || exit 255" - echo Successful test. - ) |& tee ${logfile}.test - ;; - push) - iterate_build_matrix push | - grep -v '\-test\-' | - tr '\n' '\0' | - xargs -I@ -0 bash -c "echo docker push @; \ - docker push @; \ - docker image rm @ |& grep -v 'No such image'" - ;; - *) - echo "Usage: $0 [build|test|push]" - exit 1 - ;; - esac -done diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl new file mode 100644 index 0000000000..c1498fd71a --- /dev/null +++ b/docker/docker-bake.hcl @@ -0,0 +1,47 @@ +variable "REGISTRY" { + default = "deeplabcut/deeplabcut" +} +variable "DLC_VERSION" { + default = "3.0.0rc14" +} +variable "CUDA_VERSION" { + default = "12.4" +} +variable "CUDNN_VERSION" { + default = "9" +} +variable "PYTORCH_VERSION" { + default = "2.5.1" +} +variable "MARK_LATEST" { + default = false +} +target "_common" { + context = "." + dockerfile = "Dockerfile" + args = { + DEEPLABCUT_VERSION = DLC_VERSION + CUDA_VERSION = CUDA_VERSION + CUDNN_VERSION = CUDNN_VERSION + PYTORCH_VERSION = PYTORCH_VERSION + } +} +target "core" { + inherits = ["_common"] + target = "core" + tags = concat( + ["${REGISTRY}:${DLC_VERSION}-core-cuda${CUDA_VERSION}"], + MARK_LATEST ? ["${REGISTRY}:latest"] : [] + ) +} +target "jupyter" { + inherits = ["_common"] + target = "jupyter" + tags = concat( + ["${REGISTRY}:${DLC_VERSION}-jupyter-cuda${CUDA_VERSION}"], + MARK_LATEST ? ["${REGISTRY}:latest-jupyter"] : [] + ) +} +group "default" { + targets = ["core", "jupyter"] +} From 0748560e7bf369e108838621481dcbce0d0a3110 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 21 Apr 2026 14:18:04 +0200 Subject: [PATCH 04/12] add dockerignore file --- docker/.dockerignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docker/.dockerignore diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000000..50710c2a0c --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,5 @@ +examples/ +package/ +README.md +docker-bake.hcl +.gitignore From 7547079bcd87c581b3813fca2d32484ec6e5cdb2 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 21 Apr 2026 15:05:00 +0200 Subject: [PATCH 05/12] docker update README: new dockerfile naming and buildx commands --- docker/README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/docker/README.md b/docker/README.md index 3cad275b69..b2608c8ad7 100644 --- a/docker/README.md +++ b/docker/README.md @@ -4,9 +4,9 @@ documentation contains its own user documentation on the provided docker images.** This repo contains build routines for the following official DeepLabCut docker images: -- `deeplabcut/deeplabcut:${DLC_VERSION}-base-cuda${CUDA_VERSION}-cudnn9`: Base image with DLC -- `deeplabcut/deeplabcut:${DLC_VERSION}-core-cuda${CUDA_VERSION}-cudnn9`: DLC in light mode -- `deeplabcut/deeplabcut:${DLC_VERSION}-jupyter-cuda${CUDA_VERSION}-cudnn9`: DLC with jupyter installed +- `deeplabcut/deeplabcut:latest` — default runtime image (same as the former “core” image) +- `deeplabcut/deeplabcut:latest-jupyter` — Jupyter Notebook server +- `deeplabcut/deeplabcut:${DLC_VERSION}-core-cuda${CUDA_VERSION}` and `...-jupyter-cuda...` — versioned tags All images come with Python 3.11 installed. The images are synced to DockerHub: https://hub.docker.com/r/deeplabcut/deeplabcut @@ -99,20 +99,20 @@ the container with the current user instead of root) won't be there. The `core` image can simply be run by pulling the image and using `docker run`: ```bash -docker pull deeplabcut/deeplabcut:3.0.0-core-cuda11.8-cudnn9 -docker run -it --rm --gpus all deeplabcut/deeplabcut:3.0.0-core-cuda11.8-cudnn9 +docker pull deeplabcut/deeplabcut:latest +docker run -it --rm --gpus all deeplabcut/deeplabcut:latest ``` The `jupyter` image cannot be run in the same way. Notebook servers cannot be run as the root user (which can be dangerous) without passing the `--allow-root` option, so -running `docker run deeplabcut/deeplabcut:3.0.0-jupyter-cuda11.8-cudnn9` will lead to an +running `docker run deeplabcut/deeplabcut:latest-jupyter` will lead to an error (`Running as root is not recommended. Use --allow-root to bypass`). What you can do (and we do in the `deeplabcut-docker` package) is to build a docker image with the `jupyter` image as a base. We would recommend doing this for the `core` images as well. You can create the `Dockerfile`: ```dockerfile -FROM deeplabcut/deeplabcut:3.0.0-jupyter-cuda11.8-cudnn9 +FROM deeplabcut/deeplabcut:latest-jupyter ARG UID ARG GID ARG UNAME @@ -146,26 +146,25 @@ docker run -p 127.0.0.1:8889:8888 -it --rm --gpus all my-dlc-image ## For developers -Make sure your docker daemon is running and navigate to the repository root directory. -You can build the images by running +Make sure your Docker daemon is running. From the `docker/` directory, build with +Buildx bake (see `docker-bake.hcl`): -``` -docker/build.sh build +```bash +cd docker +docker buildx bake ``` -Note that this assumes that you have rights to execute `docker build` and `docker run` commands which requires either `sudo` access or membership in the `docker` group on your local machine. If you are not in the `docker` group, run the script with the environment variable `DOCKER="sudo docker"` set to override the default docker command. +Set `MARK_LATEST=true` when building the primary CUDA variant if you want `latest` / +`latest-jupyter` tags included. Push to Docker Hub (after `docker login`): -Images can be verified by running - -``` -docker/build.sh test +```bash +docker buildx bake --push ``` -Built images can be pushed to DockerHub by running - -``` -docker/build.sh push -``` +Note that this assumes that you have rights to execute `docker build` and `docker run` +commands which requires either `sudo` access or membership in the `docker` group on +your local machine. If you are not in the `docker` group, run the bake with +`DOCKER="sudo docker"` or add your user to the `docker` group. ## Prerequisites (if you don't have Docker installed already) From 04646e6c3ba0a12b148eccd072ed610ccb12a227 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:27:15 +0200 Subject: [PATCH 06/12] Update docker/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docker/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index b2608c8ad7..d3da899009 100644 --- a/docker/README.md +++ b/docker/README.md @@ -163,8 +163,9 @@ docker buildx bake --push Note that this assumes that you have rights to execute `docker build` and `docker run` commands which requires either `sudo` access or membership in the `docker` group on -your local machine. If you are not in the `docker` group, run the bake with -`DOCKER="sudo docker"` or add your user to the `docker` group. +your local machine. If you are not in the `docker` group, run `sudo docker buildx bake` +(and `sudo docker buildx bake --push` when pushing) or add your user to the `docker` +group. ## Prerequisites (if you don't have Docker installed already) From 99d31cd6d856e5945cbf6b8e4296fce80fa27f40 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:27:26 +0200 Subject: [PATCH 07/12] Update docker/Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d26b9917d9..250af388fd 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -40,7 +40,7 @@ WORKDIR /app # ── jupyter ─────────────────────────────────────────────────────────────────── FROM core AS jupyter -RUN pip install notebook +RUN pip install "notebook<7" EXPOSE 8888 ENTRYPOINT ["jupyter", "notebook", \ "--no-browser", "--NotebookApp.token=deeplabcut", "--ip", "0.0.0.0"] From ead4b7c19f3a81b115c5b2033250cad914258fd7 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:34:46 +0200 Subject: [PATCH 08/12] Update dockerfile: fix pretrained weights directory permission --- docker/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 250af388fd..b38f2d2b6e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,8 +23,11 @@ RUN apt-get update -yy && \ RUN pip install --upgrade pip && \ pip install "deeplabcut[modelzoo,wandb]==${DEEPLABCUT_VERSION}" -RUN chmod a+rwx -R \ - /opt/conda/lib/python3.11/site-packages/deeplabcut/pose_estimation_pytorch/models/backbones/pretrained_weights +# Create pretrained weights directory and make it writable +# (same default as HuggingFaceWeightsMixin in backbones/base.py: Path(__file__).parent / "pretrained_weights") +RUN PRETRAINED=$(python -c "from pathlib import Path; import deeplabcut.pose_estimation_pytorch.models.backbones.base as b; print(Path(b.__file__).parent / 'pretrained_weights')") && \ + mkdir -p "$PRETRAINED" && \ + chmod a+rwx -R "$PRETRAINED" RUN mkdir -p /app && chmod a+rwx /app From ff549d1cf8985a5dc37ac7ffff3ca5a4ff1d3a11 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 28 Apr 2026 17:18:48 +0200 Subject: [PATCH 09/12] dockerfile: add healthcheck for jupyter exposed port --- docker/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index b38f2d2b6e..3d256d2929 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,6 +45,8 @@ FROM core AS jupyter RUN pip install "notebook<7" EXPOSE 8888 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8888/api/', timeout=3).read()" || exit 1 ENTRYPOINT ["jupyter", "notebook", \ "--no-browser", "--NotebookApp.token=deeplabcut", "--ip", "0.0.0.0"] From 8d4576474b5fc2fd510978f6089b93fdc114ecdd Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 28 Apr 2026 17:49:11 +0200 Subject: [PATCH 10/12] dockerfile: add override jupyter token option + port-expose warning in README.md --- docker/Dockerfile | 4 +++- docker/README.md | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3d256d2929..7874e9f69c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,12 +43,14 @@ WORKDIR /app # ── jupyter ─────────────────────────────────────────────────────────────────── FROM core AS jupyter +ENV NOTEBOOK_TOKEN=deeplabcut + RUN pip install "notebook<7" EXPOSE 8888 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8888/api/', timeout=3).read()" || exit 1 ENTRYPOINT ["jupyter", "notebook", \ - "--no-browser", "--NotebookApp.token=deeplabcut", "--ip", "0.0.0.0"] + "--no-browser", "--NotebookApp.token=${NOTEBOOK_TOKEN}", "--ip", "0.0.0.0"] # ── test (CI only, not published to Hub) ───────────────────────────────────── FROM core AS test diff --git a/docker/README.md b/docker/README.md index d3da899009..2cc49088a6 100644 --- a/docker/README.md +++ b/docker/README.md @@ -70,6 +70,16 @@ This is straightforward, and there are many resources you can explore on how to such as [this StackOverflow post](https://stackoverflow.com/a/69244262) or the [Jupyter Notebook docs](https://jupyter-notebook.readthedocs.io/en/4.x/public_server.html)). +```{warning} +The Jupyter image uses a fixed default access token (deeplabcut) that is publicly +known. Anyone who can reach port 8888 on your machine can execute arbitrary +code in the container. Do not expose port 8888 to the internet (e.g. via +a cloud VM's firewall or a public 0.0.0.0 binding without a reverse proxy). +For local use, bind the port to localhost only (e.g. -p 127.0.0.1:8888:8888) +and use SSH port forwarding to access the server remotely, as described below. +To use a custom token, pass -e NOTEBOOK_TOKEN= to docker run +``` + This can easily be done with `deeplabcut-docker`. To run a DeepLabCut notebook on a remote server: From b73c99f0ebb5c9ac428fa03cb389af2dfcdcaacc Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 28 Apr 2026 17:51:06 +0200 Subject: [PATCH 11/12] dockerfile: add comment regarding root-user --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7874e9f69c..9119099bb8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -41,6 +41,7 @@ ENV DEEPLABCUT_VERSION=${DEEPLABCUT_VERSION} WORKDIR /app # ── jupyter ─────────────────────────────────────────────────────────────────── +# (runs as root intentionally; deeplabcut-docker adds a host-matched user at runtime) FROM core AS jupyter ENV NOTEBOOK_TOKEN=deeplabcut From 551237bd45825570254137703cb783758b93c762 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com> Date: Fri, 15 May 2026 14:02:52 +0200 Subject: [PATCH 12/12] [Docker 2] update `deeplabcut-docker` package (#3291) * update deeplabcut_docker: replace shell script with pure python * update deeplabcut_docker: modernize setup via pyproject.toml * update deeplabcut_docker README * docker: fix default user home directory -> home/{user} * deeplabcut-docker: update print statement passw->token * deeplabcut-docker: update pyproject.toml Python range 3.10-3.12 --- docker/README.md | 17 ++- docker/package/MANIFEST.in | 4 - docker/package/deeplabcut_docker.py | 202 +++++++++++++++++++++------- docker/package/deeplabcut_docker.sh | 153 --------------------- docker/package/pyproject.toml | 31 ++++- docker/package/setup.cfg | 41 ------ 6 files changed, 190 insertions(+), 258 deletions(-) delete mode 100644 docker/package/MANIFEST.in delete mode 100755 docker/package/deeplabcut_docker.sh delete mode 100644 docker/package/setup.cfg diff --git a/docker/README.md b/docker/README.md index 2cc49088a6..6e03a281ae 100644 --- a/docker/README.md +++ b/docker/README.md @@ -51,16 +51,15 @@ when calling `docker run`: deeplabcut-docker bash --gpus all -v /home/john:/home/john ``` -You can select which DeepLabCut version and CUDA version to use through the -`DLC_VERSION` and `CUDA_VERSION` environment variables. So to launch a container with -CUDA 12.1 and DLC 3.0.0, you can run: +Use `DLC_VERSION` and `CUDA_VERSION` to select the Hub tag (unset `DLC_VERSION` uses +`latest` / `latest-jupyter`): ```bash -DLC_VERSION=3.0.0 CUDA_VERSION=12.1 deeplabcut-docker bash --gpus all +DLC_VERSION=3.0.0rc14 CUDA_VERSION=12.4 deeplabcut-docker bash --gpus all ``` -*Note: Advanced users can also directly download and use the `deeplabcut-docker.sh` -script if this is preferred over a python helper script.* +To use a specific image instead of the default tags, pass `--image repo:tag`. +Make sure that the image supports jupyter notebooks when passing `notebook`. ### Jupyter Notebooks Running on Remote Servers @@ -129,11 +128,11 @@ ARG UNAME ARG GNAME # Create same user as on the host system -RUN mkdir -p /home +RUN mkdir -p /home/${UNAME} RUN mkdir -p /app RUN groupadd -g ${GID} ${GNAME} || groupmod -o -g ${GID} ${GNAME} -RUN useradd -d /home -s /bin/bash -u ${UID} -g ${GID} ${UNAME} -RUN chown -R ${UNAME}:${GNAME} /home +RUN useradd -d /home/${UNAME} -s /bin/bash -u ${UID} -g ${GID} ${UNAME} +RUN chown -R ${UNAME}:${GNAME} /home/${UNAME} RUN chown -R ${UNAME}:${GNAME} /app WORKDIR /app diff --git a/docker/package/MANIFEST.in b/docker/package/MANIFEST.in deleted file mode 100644 index 644ae86674..0000000000 --- a/docker/package/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include pyproject.toml -include PYPI_README.md -include LICENSE -include deeplabcut_docker.sh diff --git a/docker/package/deeplabcut_docker.py b/docker/package/deeplabcut_docker.py index 296f4789d9..ffdd94dbbd 100644 --- a/docker/package/deeplabcut_docker.py +++ b/docker/package/deeplabcut_docker.py @@ -1,75 +1,177 @@ #!/usr/bin/env python3 -"""DeepLabCut2.0-2.2 Toolbox (deeplabcut.org) © A. - -& M. Mathis Labs https://github.com/DeepLabCut/DeepLabCut Please see AUTHORS for -contributors. -https://github.com/DeepLabCut/DeepLabCut/blob/master/AUTHORS -Licensed under GNU Lesser General Public License v3.0 -""" +"""Helper CLI to run DeepLabCut Docker images (LGPL-3.0).""" import argparse -import pty +import grp +import os +import platform +import pwd +import shlex +import subprocess import sys +from datetime import datetime, timezone + +__version__ = "0.0.12-alpha" + +_IMAGE = "deeplabcut/deeplabcut" +_DEFAULT_CUDA = "12.4" + + +def _docker() -> list[str]: + """Return the docker CLI argv prefix (from DOCKER env or `docker`).""" + return shlex.split(os.environ.get("DOCKER", "docker")) + + +def _log(msg: str) -> None: + """Log a timestamped message to stderr.""" + ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z") + print(f"[{ts}]: {msg}", file=sys.stderr) + + +def _check_system() -> None: + """Verify docker group membership on Linux; warn on macOS.""" + if platform.system() == "Linux": + if os.environ.get("DOCKER", "docker").strip() == "sudo docker": + return + if os.geteuid() == 0: + return + try: + docker_gid = grp.getgrnam("docker").gr_gid + except KeyError: + return + if docker_gid not in os.getgroups(): + _log(f'The current user {os.getuid()} is not in the "docker" group.') + _log('Use DOCKER="sudo docker" (with care) or add your user to "docker".') + sys.exit(1) + elif platform.system() == "Darwin": + _log("macOS support is experimental; report issues at") + _log("https://github.com/DeepLabCut/DeepLabCut/issues") + -__version__ = "0.0.11-alpha" +def _remote_tag(mode: str) -> str: + """Get the DockerHub image tag from DLC_VERSION and CUDA_VERSION env vars.""" + cuda = os.environ.get("CUDA_VERSION", _DEFAULT_CUDA) + ver = os.environ.get("DLC_VERSION", "").strip() + if mode == "notebook": + if ver: + return f"{_IMAGE}:{ver}-jupyter-cuda{cuda}" + return f"{_IMAGE}:latest-jupyter" + if ver: + return f"{_IMAGE}:{ver}-core-cuda{cuda}" + return f"{_IMAGE}:latest" -_MOTD = r""" - .--, .--, - ( ( \.---./ ) ) - '.__/o o\__.' - `{= ^ =}´ - > u < - ____________________.""`-------`"".______________________ -\ ___ __ __ _____ __ / -/ / _ \ ___ ___ ___ / / ___ _ / / / ___/__ __ / /_ \ -\ / // // -_)/ -_)/ _ \ / /__/ _ `// _ \/ /__ / // // __/ / -//____/ \__/ \__// .__//____/\_,_//_.__/\___/ \_,_/ \__/ \ -\_________________________________________________________/ - ___)( )(___ `-.___. - (((__) (__))) ~` -Welcome to DeepLabCut docker! -""" +def _warn_if_not_jupyter_image(ref: str) -> None: + """Warn if the image does not appear to have a Jupyter entrypoint.""" + r = subprocess.run( + _docker() + + [ + "image", + "inspect", + ref, + "--format", + "{{json .Config.Entrypoint}} {{json .Config.Cmd}}", + ], + capture_output=True, + text=True, + ) + if r.returncode != 0: + sys.exit(f"Could not inspect image {ref!r} after pull.\n{r.stderr.strip()}") + blob = (r.stdout or "").lower() + if "jupyter" not in blob: + _log( + f"Warning: image {ref!r} does not appear to have a Jupyter entrypoint. " + "Proceeding anyway — if the server fails to start, ensure the image " + "exposes a Jupyter-compatible entrypoint on port 8888." + ) + + +def _build_user_image(remote: str, local: str) -> None: + """Build a small local image on top of remote with the current UID/GID user.""" + try: + uid, gid = os.getuid(), os.getgid() + except AttributeError: + sys.exit("deeplabcut-docker requires a POSIX system (Linux or macOS).") + user = pwd.getpwuid(uid).pw_name + group = grp.getgrgid(gid).gr_name + _log(f"Configuring a local image for user {user} ({uid}) in group {group} ({gid})") + dockerfile = ( + "\n".join( + ( + f"FROM {remote}", + f"RUN mkdir -p /home/{user} /app", + f"RUN groupadd -g {gid} {group} || groupmod -o -g {gid} {group}", + f"RUN useradd -d /home/{user} -s /bin/bash -u {uid} -g {gid} {user}", + f"RUN chown -R {user}:{group} /home/{user} /app", + f"USER {user}", + ) + ) + + "\n" + ) + subprocess.run( + _docker() + ["build", "-q", "-t", local, "-"], + input=dockerfile.encode(), + check=True, + ) + _log("Build succeeded") -def _parse_args(): +def _parse_args() -> tuple[argparse.Namespace, list[str]]: + """Parse CLI args and return (namespace, extra args for docker run).""" parser = argparse.ArgumentParser( - "deeplabcut-docker", + prog="deeplabcut-docker", description=( - "Utility tool for launching DeepLabCut docker containers. " - "Only a single argument is given to specify the container type. " - "By default, the current directory is mounted into the container " - "and used as the current working directory. You can additionally " - "specify any additional docker argument specified in " - "https://docs.docker.com/engine/reference/commandline/cli/." + "Launch DeepLabCut Docker containers. The current directory is mounted " + "at /app and used as the working directory. Additional arguments are " + "passed through to `docker run` (see " + "https://docs.docker.com/engine/reference/commandline/cli/)." ), ) parser.add_argument( "container", - type=str, - choices=["notebook", "bash"], + choices=("notebook", "bash"), help=( - "The container to launch. A list of all containers is available on " - "https://hub.docker.com/r/deeplabcut/deeplabcut/tags. By default, the " - "latest DLC version will be selected and automatically updated, if " - "possible. All containers are currently launched in interactive mode " - "by default, meaning you can use Ctrl+C in your terminal session to " - "terminate a command." + "notebook: Jupyter server; bash: interactive shell. " + "Image tags: https://hub.docker.com/r/deeplabcut/deeplabcut/tags — " + "use DLC_VERSION and CUDA_VERSION to select a versioned tag; unset " + "DLC_VERSION uses latest / latest-jupyter." + ), + ) + parser.add_argument( + "--image", + metavar="REF", + help=( + "Use this image (name:tag or digest) instead of the default from " + "DLC_VERSION / CUDA_VERSION. For notebook, the image is checked for " + "a Jupyter Notebook entrypoint after pull." ), ) return parser.parse_known_args() -def main(): - """Main entry point. +def main() -> None: + """Entry point: pull, user-layer build, and run the container.""" + _check_system() + args, docker_run_args = _parse_args() + mode = args.container + + remote = args.image or _remote_tag(mode) + local = f"deeplabcut-local-{mode}" + subprocess.run(_docker() + ["pull", remote], check=True) + if mode == "notebook" and args.image: + _warn_if_not_jupyter_image(remote) + _build_user_image(remote, local) - Parse arguments and launch container. - """ - launch_args, docker_arguments = _parse_args() - argv = ["deeplabcut_docker.sh", launch_args.container, *docker_arguments] - print(_MOTD, file=sys.stderr) - pty.spawn(argv) - print("Container stopped.", file=sys.stderr) + run = _docker() + ["run", "-it", "--rm", "-v", f"{os.getcwd()}:/app", "-w", "/app"] + if mode == "notebook": + port = os.environ.get("DLC_NOTEBOOK_PORT", "8888") + _log("Starting the notebook server.") + _log(f"Open your browser at http://127.0.0.1:{port}") + _log("If prompted for a token, enter 'deeplabcut' (default).") + _log("To use a custom token: add -e NOTEBOOK_TOKEN= to your arguments.") + run += ["-p", f"127.0.0.1:{port}:8888"] + run += docker_run_args + [local] + ([] if mode == "notebook" else ["bash"]) + sys.exit(subprocess.run(run).returncode) if __name__ == "__main__": diff --git a/docker/package/deeplabcut_docker.sh b/docker/package/deeplabcut_docker.sh deleted file mode 100755 index 05c929c450..0000000000 --- a/docker/package/deeplabcut_docker.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/bash -# -# Helper script for launching deeplabcut docker UI containers -# Usage: -# $ ./deeplabcut-docker.sh [notebook|bash] - -DOCKER=${DOCKER:-docker} -CUDA_VERSION=${CUDA_VERSION:-"12.4"} -CUDNN_VERSION=${CUDNN_VERSION:-"9"} -DLC_VERSION=${DLC_VERSION:-"3.0.0"} -DLC_NOTEBOOK_PORT=${DLC_NOTEBOOK_PORT:-8888} - -# Check if the current users has privileges to start -# a docker container. -check_system() { - if [[ $(uname -s) == Linux ]]; then - if [ $(groups | grep -c docker) -eq 0 ]; then - if [[ "$DOCKER" == "sudo docker" ]]; then - return 0 - fi - err "The current user $(id -u) is not " - err "part of the \"docker\" group. " - err "Please either: " - err " 1) Launch this script with the DOCKER environment " - err " variable set to DOCKER=\"sudo docker\" (use this " - err " with care)! " - err " 2) Add your user to the docker group. You might need " - err " to log in and out again to see the effect of the " - err " change. " - exit 1 - fi - elif [[ $(uname -s) == Darwin ]]; then - err "Please note that macOSX support is currently experimental" - err "If you encounter errors, please open an issue on" - err "https://github.com/DeepLabCut/DeepLabCut/issues" - err "Thanks for testing the package!" - fi -} - -get_mount_args() { - args=( - "-v $(pwd):/app -w /app" - ) - echo "${args[@]}" -} - -get_container_name() { - echo "deeplabcut/deeplabcut:${DLC_VERSION}-$1-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION}" -} - -get_local_container_name() { - echo "deeplabcut-${DLC_VERSION}-$1-cuda${CUDA_VERSION}-cudnn${CUDNN_VERSION}" -} - -### Start of helper functions ### - -# Print error messages to stderr -# Ref. https://google.github.io/styleguide/shellguide.html#stdout-vs-stderr -err() { - echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 -} - -# Update the docker container -update() { - $DOCKER pull $(get_container_name $1) -} - -# Build the docker container -# Usage: build [core|jupyter] -build() { - tag=$1 - _build $(get_container_name $tag) $(get_local_container_name $tag) || exit 1 -} - -_build() { - remote_name=$1 - local_name=$2 - - uname=$(id -un) - uid=$(id -u) - gname=$(id -gn) - gid=$(id -g) - - err "Configuring a local container for user $uname ($uid) in group $gname ($gid)" - $DOCKER build -q -t "${local_name}" - <=77", "wheel" ] + +[project] +name = "deeplabcut-docker" +description = "A helper package to launch DeepLabCut docker images" +readme = { file = "PYPI_README.md", content-type = "text/markdown" } +license = "LGPL-3.0-or-later" +license-files = [ "LICENSE" ] +authors = [ + { name = "M-Lab of Adaptive Intelligence", email = "mackenzie@deeplabcut.org" }, + { name = "Mathis Group for Computational Neuroscience and AI", email = "alexander@deeplabcut.org" }, +] +requires-python = ">=3.10,<3.13" +classifiers = [ + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Utilities", +] +dynamic = [ "version" ] +urls."Bug Tracker" = "https://github.com/DeepLabCut/DeepLabCut/issues" +urls.Homepage = "https://github.com/DeepLabCut/DeepLabCut/tree/main/docker" +scripts.deeplabcut-docker = "deeplabcut_docker:main" + +[tool.setuptools] +py-modules = [ "deeplabcut_docker" ] +dynamic.version = { attr = "deeplabcut_docker.__version__" } diff --git a/docker/package/setup.cfg b/docker/package/setup.cfg deleted file mode 100644 index 00d64ed987..0000000000 --- a/docker/package/setup.cfg +++ /dev/null @@ -1,41 +0,0 @@ -[metadata] -name = deeplabcut-docker -version = attr: deeplabcut_docker.__version__ -author = A. & M. Mathis Labs -author_email = alexander@deeplabcut.org -maintainer = Steffen Schneider -maintainer_email = stes@hey.com -description = A helper package to launch DeepLabCut docker images -url = https://github.com/DeepLabCut/DeepLabCut/tree/main/docker -project_urls = - Bug Tracker = https://github.com/DeepLabCut/DeepLabCut/issues -classifiers = - Programming Language :: Python :: 3 - License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) - Operating System :: MacOS - Operating System :: POSIX :: Linux - Topic :: Utilities -license = LGPLv3 -long_description = file: PYPI_README.md -long_description_content_type = text/markdown -platform = any - -[options] -package_dir = - = . -py_modules = deeplabcut_docker -python_requires = >=3.10 -include_package_data = True - -[options.entry_points] -console_scripts = - deeplabcut-docker = deeplabcut_docker:main - -[options.packages.find] -where = . - -[options.data_files] -bin = deeplabcut_docker.sh - -[bdist_wheel] -universal = 1