From 7adcdbd572e4f094386aa3e2464f2279a0867cc1 Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Fri, 10 Jan 2025 23:21:09 -0500 Subject: [PATCH 1/9] Preliminary work to build multiple versions and avoid venv collision. Dockerfile are generated by the Makefile from a common base (also used to name the container) --- .../Dockerfile-ubuntu22_cuda12.3.2 | 6 +- Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 | 86 +++++++++ Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 | 85 ++++++++ Makefile | 182 +++++++++++------- components/base-ubuntu22_cuda12.3.2 | 19 ++ components/base-ubuntu22_cuda12.4.1 | 17 ++ components/base-ubuntu24_cuda12.5.1 | 16 ++ components/part1-common | 69 +++++++ init.bash | 45 +++++ 9 files changed, 456 insertions(+), 69 deletions(-) rename Dockerfile => Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 (92%) create mode 100644 Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 create mode 100644 Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 create mode 100644 components/base-ubuntu22_cuda12.3.2 create mode 100644 components/base-ubuntu22_cuda12.4.1 create mode 100644 components/base-ubuntu24_cuda12.5.1 create mode 100644 components/part1-common diff --git a/Dockerfile b/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 similarity index 92% rename from Dockerfile rename to Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 index ccc6e51..4d02fac 100644 --- a/Dockerfile +++ b/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 @@ -1,6 +1,6 @@ -ARG DOCKER_FROM=nvidia/cuda:12.3.2-runtime-ubuntu22.04 -FROM ${DOCKER_FROM} +FROM nvidia/cuda:12.3.2-runtime-ubuntu22.04 +# Here, we are using CUDNN8 (devel) -- CUDNN9 is also compatible for CUDA 12.3 # Adapted from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.2.2/ubuntu2204/devel/cudnn8/Dockerfile ENV NV_CUDNN_VERSION=8.9.7.29 ENV NV_CUDNN_PACKAGE_NAME="libcudnn8" @@ -15,6 +15,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-mark hold ${NV_CUDNN_PACKAGE_NAME} \ && rm -rf /var/lib/apt/lists/* +ARG BASE_DOCKER_FROM=nvidia/cuda:12.3.2-runtime-ubuntu22.04 + ##### Base # Install system packages diff --git a/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 b/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 new file mode 100644 index 0000000..43bf068 --- /dev/null +++ b/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 @@ -0,0 +1,86 @@ +FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 + +# CUDNN9 "runtime" package +# Adapted from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.4.1/ubuntu2204/runtime/cudnn/Dockerfile +ENV NV_CUDNN_VERSION=9.1.0.70-1 +ENV NV_CUDNN_PACKAGE_NAME=libcudnn9-cuda-12 +ENV NV_CUDNN_PACKAGE="libcudnn9-cuda-12=${NV_CUDNN_VERSION}" + +LABEL com.nvidia.cudnn.version="${NV_CUDNN_VERSION}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ${NV_CUDNN_PACKAGE} \ + && apt-mark hold ${NV_CUDNN_PACKAGE_NAME} \ + && rm -rf /var/lib/apt/lists/* + +ARG BASE_DOCKER_FROM=nvidia/cuda:12.4.1-runtime-ubuntu22.04 + +##### Base + +# Install system packages +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update -y --fix-missing\ + && apt-get install -y \ + apt-utils \ + locales \ + ca-certificates \ + && apt-get upgrade -y \ + && apt-get clean + +# UTF-8 +RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +ENV LANG=en_US.utf8 +ENV LC_ALL=C + +# Install needed packages +RUN apt-get update -y --fix-missing \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + python3-dev \ + unzip \ + wget \ + zip \ + zlib1g \ + zlib1g-dev \ + gnupg \ + rsync \ + python3-pip \ + python3-venv \ + git \ + sudo \ + libgl1 \ + libglib2.0-0 \ + && apt-get clean + +ENV BUILD_FILE="/etc/image_base.txt" +ARG BASE_DOCKER_FROM +RUN echo "DOCKER_FROM: ${BASE_DOCKER_FROM}" | tee ${BUILD_FILE} +RUN echo "CUDNN: ${NV_CUDNN_PACKAGE_NAME} (${NV_CUDNN_VERSION})" | tee -a ${BUILD_FILE} + +ARG BUILD_BASE="unknown" +LABEL comfyui-nvidia-docker-build-from=${BUILD_BASE} +RUN it="/etc/build_base.txt"; echo ${BUILD_BASE} > $it && chmod 555 $it + +##### ComfyUI preparation +# The comfy user will have UID 1024 and GID 1024 +ENV COMFYUSER_DIR="/comfy" +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && useradd -u 1024 -U -d ${COMFYUSER_DIR} -s /bin/bash -m comfy \ + && usermod -G users comfy \ + && adduser comfy sudo \ + && test -d ${COMFYUSER_DIR} +RUN it="/etc/comfyuser_dir"; echo ${COMFYUSER_DIR} > $it && chmod 555 $it + +ENV NVIDIA_VISIBLE_DEVICES=all + +EXPOSE 8188 + +USER comfy +WORKDIR ${COMFYUSER_DIR} +COPY --chown=comfy:comfy --chmod=555 init.bash comfyui-nvidia_init.bash + +ARG BUILD_DATE="unknown" +LABEL comfyui-nvidia-docker-build=${BUILD_DATE} + +CMD [ "./comfyui-nvidia_init.bash" ] diff --git a/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 b/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 new file mode 100644 index 0000000..aa3cf99 --- /dev/null +++ b/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 @@ -0,0 +1,85 @@ +FROM nvidia/cuda:12.5.1-runtime-ubuntu24.04 + +# Extended from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.5.1/ubuntu2404/runtime/Dockerfile +ENV NV_CUDNN_VERSION=9.3.0.75-1 +ENV NV_CUDNN_PACKAGE_NAME="libcudnn9" +ENV NV_CUDA_ADD=cuda-12 +ENV NV_CUDNN_PACKAGE="$NV_CUDNN_PACKAGE_NAME-$NV_CUDA_ADD=$NV_CUDNN_VERSION" + +LABEL com.nvidia.cudnn.version="${NV_CUDNN_VERSION}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ${NV_CUDNN_PACKAGE} \ + && apt-mark hold ${NV_CUDNN_PACKAGE_NAME}-${NV_CUDA_ADD} + +ARG BASE_DOCKER_FROM=nvidia/cuda:12.5.1-runtime-ubuntu24.04 + +##### Base + +# Install system packages +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update -y --fix-missing\ + && apt-get install -y \ + apt-utils \ + locales \ + ca-certificates \ + && apt-get upgrade -y \ + && apt-get clean + +# UTF-8 +RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +ENV LANG=en_US.utf8 +ENV LC_ALL=C + +# Install needed packages +RUN apt-get update -y --fix-missing \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + python3-dev \ + unzip \ + wget \ + zip \ + zlib1g \ + zlib1g-dev \ + gnupg \ + rsync \ + python3-pip \ + python3-venv \ + git \ + sudo \ + libgl1 \ + libglib2.0-0 \ + && apt-get clean + +ENV BUILD_FILE="/etc/image_base.txt" +ARG BASE_DOCKER_FROM +RUN echo "DOCKER_FROM: ${BASE_DOCKER_FROM}" | tee ${BUILD_FILE} +RUN echo "CUDNN: ${NV_CUDNN_PACKAGE_NAME} (${NV_CUDNN_VERSION})" | tee -a ${BUILD_FILE} + +ARG BUILD_BASE="unknown" +LABEL comfyui-nvidia-docker-build-from=${BUILD_BASE} +RUN it="/etc/build_base.txt"; echo ${BUILD_BASE} > $it && chmod 555 $it + +##### ComfyUI preparation +# The comfy user will have UID 1024 and GID 1024 +ENV COMFYUSER_DIR="/comfy" +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && useradd -u 1024 -U -d ${COMFYUSER_DIR} -s /bin/bash -m comfy \ + && usermod -G users comfy \ + && adduser comfy sudo \ + && test -d ${COMFYUSER_DIR} +RUN it="/etc/comfyuser_dir"; echo ${COMFYUSER_DIR} > $it && chmod 555 $it + +ENV NVIDIA_VISIBLE_DEVICES=all + +EXPOSE 8188 + +USER comfy +WORKDIR ${COMFYUSER_DIR} +COPY --chown=comfy:comfy --chmod=555 init.bash comfyui-nvidia_init.bash + +ARG BUILD_DATE="unknown" +LABEL comfyui-nvidia-docker-build=${BUILD_DATE} + +CMD [ "./comfyui-nvidia_init.bash" ] diff --git a/Makefile b/Makefile index ed834b5..95f5f6f 100644 --- a/Makefile +++ b/Makefile @@ -2,90 +2,138 @@ SHELL := /bin/bash .PHONY: all DOCKER_CMD=docker - -DOCKER_FROM=nvidia/cuda:12.3.2-runtime-ubuntu22.04 - -BUILD_DATE=$(shell printf '%(%Y%m%d_%H%M)T' -1) -BUILD_BASE=ubuntu22_cuda12.3 - -COMFYUI_CONTAINER_NAME=comfyui-nvidia-docker -BUILD_TAG=${BUILD_BASE}-latest -NAMED_BUILD=${COMFYUI_CONTAINER_NAME}:${BUILD_TAG} -NAMED_BUILD_LATEST=${COMFYUI_CONTAINER_NAME}:latest - -DOCKERFILE=Dockerfile DOCKER_PRE="NVIDIA_VISIBLE_DEVICES=all" +DOCKER_BUILD_ARGS= +##DOCKER_BUILD_ARGS="--no-cache" +BUILD_DATE=$(shell printf '%(%Y%m%d)T' -1) -DOCKER_BUILD_ARGS= -#DOCKER_BUILD_ARGS="--no-cache" +COMFYUI_CONTAINER_NAME=comfyui-nvidia-docker -# Set to False to make it less verbose -VERBOSE_PRINT=True +COMPONENTS_DIR=components +DOCKERFILE_DIR=Dockerfile -##### +# Get the list of all the base- files in COMPONENTS_DIR +DOCKER_ALL=$(shell ls -1 ${COMPONENTS_DIR}/base-* | perl -pe 's%^.+/base-%%' | sort) all: - @echo "** Available Docker images to be built (make targets):" - @echo "latest: builds ${NAMED_BUILD} and tags it as ${NAMED_BUILD_LATEST}" + @echo "Available ${COMFYUI_CONTAINER_NAME} ${DOCKER_CMD} images to be built (make targets):" + @echo -n " "; echo ${DOCKER_ALL} | sed -e 's/ /\n /g' @echo "" - @echo "build: builds latest" - -##### latest + @echo "build: builds all" -build: - @make latest +build: ${DOCKER_ALL} +${DOCKERFILE_DIR}: + @mkdir -p ${DOCKERFILE_DIR} -latest: - @VAR_NT=${COMFYUI_CONTAINER_NAME}-${BUILD_TAG} USED_BUILD=${NAMED_BUILD} USED_BUILD_LATEST=${NAMED_BUILD_LATEST} make build_main_actual - - -build_main_actual: - @echo "== [${USED_BUILD}] ==" +${DOCKER_ALL}: ${DOCKERFILE_DIR} + @echo ""; echo ""; echo "===== Building ${COMFYUI_CONTAINER_NAME}:$@" + @cat ${COMPONENTS_DIR}/base-$@ > ${DOCKERFILE_DIR}/Dockerfile-$@ + @cat ${COMPONENTS_DIR}/part1-common >> ${DOCKERFILE_DIR}/Dockerfile-$@ + @$(eval VAR_NT="${COMFYUI_CONTAINER_NAME}-$@") @echo "-- Docker command to be run:" - @echo "BUILDX_EXPERIMENTAL=1 ${DOCKER_PRE} docker buildx debug --on=error build --progress plain --platform linux/amd64 ${DOCKER_BUILD_ARGS} \\" > ${VAR_NT}.cmd - @echo " --build-arg DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd - @echo " --build-arg BASE_DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd + @${eval CND_BUILDX="${COMFYUI_CONTAINER_NAME}"} + @echo "docker buildx ls | grep -q ${CND_BUILDX} && echo \"builder already exists -- to delete it, use: docker buildx rm ${CND_BUILDX}\" || docker buildx create --name ${CND_BUILDX}" > ${VAR_NT}.cmd + @echo "docker buildx use ${CND_BUILDX} || exit 1" >> ${VAR_NT}.cmd + @echo "BUILDX_EXPERIMENTAL=1 ${DOCKER_PRE} docker buildx debug --on=error build --progress plain --platform linux/amd64 ${DOCKER_BUILD_ARGS} \\" >> ${VAR_NT}.cmd @echo " --build-arg BUILD_DATE=\"${BUILD_DATE}\" \\" >> ${VAR_NT}.cmd - @echo " --build-arg BUILD_BASE=\"${BUILD_BASE}\" \\" >> ${VAR_NT}.cmd - @echo " --tag=\"${USED_BUILD}\" \\" >> ${VAR_NT}.cmd - @echo " -f ${DOCKERFILE} \\" >> ${VAR_NT}.cmd + @echo " --build-arg BUILD_BASE=\"$@\" \\" >> ${VAR_NT}.cmd + @echo " --tag=\"${COMFYUI_CONTAINER_NAME}:$@\" \\" >> ${VAR_NT}.cmd + @echo " -f ${DOCKERFILE_DIR}/Dockerfile-$@ \\" >> ${VAR_NT}.cmd + @echo " --load \\" >> ${VAR_NT}.cmd @echo " ." >> ${VAR_NT}.cmd - @cat ${VAR_NT}.cmd | tee ${VAR_NT}.log.temp + @echo "" | tee -a ${VAR_NT}.log.temp + @echo "Press Ctl+c within 5 seconds to cancel" + @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" +# Actual build @chmod +x ./${VAR_NT}.cmd @script -a -e -c ./${VAR_NT}.cmd ${VAR_NT}.log.temp; exit "$${PIPESTATUS[0]}" - @mv ${VAR_NT}.log.temp ${VAR_NT}.log @rm -f ./${VAR_NT}.cmd - @${DOCKER_CMD} tag ${USED_BUILD} ${USED_BUILD_LATEST} - - -##### clean - -docker_rmi: - docker rmi --force ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} - - -############################################## For maintainer only -##### push -DOCKERHUB_REPO="mmartial" - -docker_tag: - @make latest - @${DOCKER_CMD} tag ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} - @${DOCKER_CMD} tag ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} - @make docker_tag_list - -docker_tag_list: - @echo "Docker images tagged:" - @${DOCKER_CMD} images --filter "label=comfyui-nvidia-docker-build" - -docker_push: - @make docker_tag - @echo "hub.docker.com upload -- Press Ctl+c within 5 seconds to cancel -- will only work for maintainers" - @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" - @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD} - @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} +# +#BUILD_BASE=ubuntu22_cuda12.3 +# +#BUILD_TAG=${BUILD_BASE}-latest +#NAMED_BUILD=${COMFYUI_CONTAINER_NAME}:${BUILD_TAG} +#NAMED_BUILD_LATEST=${COMFYUI_CONTAINER_NAME}:latest +# +#DOCKERFILE=Dockerfile +#DOCKER_PRE="NVIDIA_VISIBLE_DEVICES=all" +# +# +#DOCKER_BUILD_ARGS= +##DOCKER_BUILD_ARGS="--no-cache" +# +## Set to False to make it less verbose +#VERBOSE_PRINT=True +# +###### +# +#all: +# @echo "** Available Docker images to be built (make targets):" +# @echo "latest: builds ${NAMED_BUILD} and tags it as ${NAMED_BUILD_LATEST}" +# @echo "" +# @echo "build: builds latest" +# +###### latest +# +#build: +# @make latest +# +# +#latest: +# @VAR_NT=${COMFYUI_CONTAINER_NAME}-${BUILD_TAG} USED_BUILD=${NAMED_BUILD} USED_BUILD_LATEST=${NAMED_BUILD_LATEST} make build_main_actual +# +# +#build_main_actual: +# @echo "== [${USED_BUILD}] ==" +# @echo "-- Docker command to be run:" +# @echo "BUILDX_EXPERIMENTAL=1 ${DOCKER_PRE} docker buildx debug --on=error build --progress plain --platform linux/amd64 ${DOCKER_BUILD_ARGS} \\" > ${VAR_NT}.cmd +# @echo " --build-arg DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd +# @echo " --build-arg BASE_DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd +# @echo " --build-arg BUILD_DATE=\"${BUILD_DATE}\" \\" >> ${VAR_NT}.cmd +# @echo " --build-arg BUILD_BASE=\"${BUILD_BASE}\" \\" >> ${VAR_NT}.cmd +# @echo " --tag=\"${USED_BUILD}\" \\" >> ${VAR_NT}.cmd +# @echo " -f ${DOCKERFILE} \\" >> ${VAR_NT}.cmd +# @echo " ." >> ${VAR_NT}.cmd +# +# @cat ${VAR_NT}.cmd | tee ${VAR_NT}.log.temp +# @chmod +x ./${VAR_NT}.cmd +# @script -a -e -c ./${VAR_NT}.cmd ${VAR_NT}.log.temp; exit "$${PIPESTATUS[0]}" +# +# @mv ${VAR_NT}.log.temp ${VAR_NT}.log +# @rm -f ./${VAR_NT}.cmd +# +# @${DOCKER_CMD} tag ${USED_BUILD} ${USED_BUILD_LATEST} +# +# +###### clean +# +#docker_rmi: +# docker rmi --force ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} +# +# +############################################### For maintainer only +###### push +#DOCKERHUB_REPO="mmartial" +# +#docker_tag: +# @make latest +# @${DOCKER_CMD} tag ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} +# @${DOCKER_CMD} tag ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} +# @make docker_tag_list +# +#docker_tag_list: +# @echo "Docker images tagged:" +# @${DOCKER_CMD} images --filter "label=comfyui-nvidia-docker-build" +# +#docker_push: +# @make docker_tag +# @echo "hub.docker.com upload -- Press Ctl+c within 5 seconds to cancel -- will only work for maintainers" +# @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" +# @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD} +# @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} +# \ No newline at end of file diff --git a/components/base-ubuntu22_cuda12.3.2 b/components/base-ubuntu22_cuda12.3.2 new file mode 100644 index 0000000..685112d --- /dev/null +++ b/components/base-ubuntu22_cuda12.3.2 @@ -0,0 +1,19 @@ +FROM nvidia/cuda:12.3.2-runtime-ubuntu22.04 + +# Here, we are using CUDNN8 (devel) -- CUDNN9 is also compatible for CUDA 12.3 +# Adapted from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.2.2/ubuntu2204/devel/cudnn8/Dockerfile +ENV NV_CUDNN_VERSION=8.9.7.29 +ENV NV_CUDNN_PACKAGE_NAME="libcudnn8" +ENV NV_CUDA_ADD=cuda12.2 +ENV NV_CUDNN_PACKAGE="$NV_CUDNN_PACKAGE_NAME=$NV_CUDNN_VERSION-1+$NV_CUDA_ADD" +ENV NV_CUDNN_PACKAGE_DEV="$NV_CUDNN_PACKAGE_NAME-dev=$NV_CUDNN_VERSION-1+$NV_CUDA_ADD" +LABEL com.nvidia.cudnn.version="${NV_CUDNN_VERSION}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ${NV_CUDNN_PACKAGE} \ + ${NV_CUDNN_PACKAGE_DEV} \ + && apt-mark hold ${NV_CUDNN_PACKAGE_NAME} \ + && rm -rf /var/lib/apt/lists/* + +ARG BASE_DOCKER_FROM=nvidia/cuda:12.3.2-runtime-ubuntu22.04 + diff --git a/components/base-ubuntu22_cuda12.4.1 b/components/base-ubuntu22_cuda12.4.1 new file mode 100644 index 0000000..bbcd4ef --- /dev/null +++ b/components/base-ubuntu22_cuda12.4.1 @@ -0,0 +1,17 @@ +FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 + +# CUDNN9 "runtime" package +# Adapted from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.4.1/ubuntu2204/runtime/cudnn/Dockerfile +ENV NV_CUDNN_VERSION=9.1.0.70-1 +ENV NV_CUDNN_PACKAGE_NAME=libcudnn9-cuda-12 +ENV NV_CUDNN_PACKAGE="libcudnn9-cuda-12=${NV_CUDNN_VERSION}" + +LABEL com.nvidia.cudnn.version="${NV_CUDNN_VERSION}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ${NV_CUDNN_PACKAGE} \ + && apt-mark hold ${NV_CUDNN_PACKAGE_NAME} \ + && rm -rf /var/lib/apt/lists/* + +ARG BASE_DOCKER_FROM=nvidia/cuda:12.4.1-runtime-ubuntu22.04 + diff --git a/components/base-ubuntu24_cuda12.5.1 b/components/base-ubuntu24_cuda12.5.1 new file mode 100644 index 0000000..d4686fa --- /dev/null +++ b/components/base-ubuntu24_cuda12.5.1 @@ -0,0 +1,16 @@ +FROM nvidia/cuda:12.5.1-runtime-ubuntu24.04 + +# Extended from https://gitlab.com/nvidia/container-images/cuda/-/blob/master/dist/12.5.1/ubuntu2404/runtime/Dockerfile +ENV NV_CUDNN_VERSION=9.3.0.75-1 +ENV NV_CUDNN_PACKAGE_NAME="libcudnn9" +ENV NV_CUDA_ADD=cuda-12 +ENV NV_CUDNN_PACKAGE="$NV_CUDNN_PACKAGE_NAME-$NV_CUDA_ADD=$NV_CUDNN_VERSION" + +LABEL com.nvidia.cudnn.version="${NV_CUDNN_VERSION}" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ${NV_CUDNN_PACKAGE} \ + && apt-mark hold ${NV_CUDNN_PACKAGE_NAME}-${NV_CUDA_ADD} + +ARG BASE_DOCKER_FROM=nvidia/cuda:12.5.1-runtime-ubuntu24.04 + diff --git a/components/part1-common b/components/part1-common new file mode 100644 index 0000000..20f8352 --- /dev/null +++ b/components/part1-common @@ -0,0 +1,69 @@ +##### Base + +# Install system packages +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update -y --fix-missing\ + && apt-get install -y \ + apt-utils \ + locales \ + ca-certificates \ + && apt-get upgrade -y \ + && apt-get clean + +# UTF-8 +RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +ENV LANG=en_US.utf8 +ENV LC_ALL=C + +# Install needed packages +RUN apt-get update -y --fix-missing \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + python3-dev \ + unzip \ + wget \ + zip \ + zlib1g \ + zlib1g-dev \ + gnupg \ + rsync \ + python3-pip \ + python3-venv \ + git \ + sudo \ + libgl1 \ + libglib2.0-0 \ + && apt-get clean + +ENV BUILD_FILE="/etc/image_base.txt" +ARG BASE_DOCKER_FROM +RUN echo "DOCKER_FROM: ${BASE_DOCKER_FROM}" | tee ${BUILD_FILE} +RUN echo "CUDNN: ${NV_CUDNN_PACKAGE_NAME} (${NV_CUDNN_VERSION})" | tee -a ${BUILD_FILE} + +ARG BUILD_BASE="unknown" +LABEL comfyui-nvidia-docker-build-from=${BUILD_BASE} +RUN it="/etc/build_base.txt"; echo ${BUILD_BASE} > $it && chmod 555 $it + +##### ComfyUI preparation +# The comfy user will have UID 1024 and GID 1024 +ENV COMFYUSER_DIR="/comfy" +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && useradd -u 1024 -U -d ${COMFYUSER_DIR} -s /bin/bash -m comfy \ + && usermod -G users comfy \ + && adduser comfy sudo \ + && test -d ${COMFYUSER_DIR} +RUN it="/etc/comfyuser_dir"; echo ${COMFYUSER_DIR} > $it && chmod 555 $it + +ENV NVIDIA_VISIBLE_DEVICES=all + +EXPOSE 8188 + +USER comfy +WORKDIR ${COMFYUSER_DIR} +COPY --chown=comfy:comfy --chmod=555 init.bash comfyui-nvidia_init.bash + +ARG BUILD_DATE="unknown" +LABEL comfyui-nvidia-docker-build=${BUILD_DATE} + +CMD [ "./comfyui-nvidia_init.bash" ] diff --git a/init.bash b/init.bash index cb5eba5..0e1dcd2 100644 --- a/init.bash +++ b/init.bash @@ -85,6 +85,8 @@ if test -z ${COMFYUSER_DIR}; then error_exit "Empty COMFYUSER_DIR variable"; fi it=/etc/build_base.txt if [ ! -f $it ]; then error_exit "$it missing, exiting"; fi BUILD_BASE=`cat $it` +BUILD_BASE_FILE=$it +BUILD_BASE_SPECIAL="ubuntu22_cuda12.3" # this is a special value: when this feature was introduced, will be used to mark exisitng venv if the marker is not present echo "-- BUILD_BASE: \"${BUILD_BASE}\"" if test -z ${BUILD_BASE}; then error_exit "Empty BUILD_BASE variable"; fi @@ -122,6 +124,10 @@ if [ ! -z "$WANTED_UID" -a "$WANTED_UID" != "$new_uid" ]; then echo "Wrong UID ( # We are now running as comfy echo "== Running as comfy" +# Confirm we can write to the user directory +echo "== Testing write access as the comfy user to the run directory" +it=${COMFYUSER_DIR}/mnt/.testfile; touch $it && rm -f ${COMFYUSER_DIR}/mnt/.testfile || error_exit "Failed to write to ${COMFYUSER_DIR}/mnt" + # Obtain the latest version of ComfyUI if not already present cd ${COMFYUSER_DIR}/mnt if [ ! -d "ComfyUI" ]; then @@ -129,16 +135,55 @@ if [ ! -d "ComfyUI" ]; then git clone https://github.com/comfyanonymous/ComfyUI.git ComfyUI || error_exit "ComfyUI clone failed" fi +# Confirm the ComfyUI directory is present and we can write to it +if [ ! -d "ComfyUI" ]; then error_exit "ComfyUI not found"; fi +it=ComfyUI/.testfile && rm -f $it || error_exit "Failed to write to ComfyUI directory as the comfy user" + if [ ! -d HF ]; then echo "== Creating HF directory" mkdir -p HF fi export HF_HOME=${COMFYUSER_DIR}/mnt/HF +# Confirm the HF directory is present and we can write to it +if [ ! -d "HF" ]; then error_exit "HF not found"; fi +it=HF/.testfile && rm -f $it || error_exit "Failed to write to HF directory as the comfy user" + +# Attempting to support multiple build bases +# the venv directory is specific to the build base +# we are placing a marker file in the venv directory to match it to a build base +# if the marker is not for container's build base, we rename the venv directory to avoid conflicts + +# if a venv is present, confirm we can write to it +if [ -d "venv" ]; then + it=venv/.testfile && rm -f $it || error_exit "Failed to write to venv directory as the comfy user" + # use the special value to mark existing venv if the marker is not present + it=venv/.build_base.txt; if [ ! -f $it ]; then echo $BUILD_BASE_SPECIAL > $it; fi +fi + +# Check for an existing venv; if present, is it the proper one -- ie does its .build_base.txt match the container's BUILD_BASE_FILE? +if [ -d venv ]; then + it=venv/.build_base.txt + venv_bb=`cat $it` + + if cmp --silent $it $BUILD_BASE_FILE; then + echo "== venv is for this BUILD_BASE (${BUILD_BASE})" + else + echo "== venv ($venv_bb) is not for this BUILD_BASE (${BUILD_BASE}), renaming it and seeing if a valid one is present" + mv venv venv-${venv_bb} || error_exit "Failed to rename venv to venv-${venv_bb}" + + if [ -d venv-${BUILD_BASE} ]; then + echo "== Existing venv (${BUILD_BASE}) found, attempting to use it" + mv venv-${BUILD_BASE} venv || error_exit "Failed to rename ven-${BUILD_BASE} to venv" + fi + fi +fi + # virtualenv for installation if [ ! -d "venv" ]; then echo "== Creating virtualenv" python3 -m venv venv || error_exit "Virtualenv creation failed" + echo $BUILD_BASE > venv/.build_base.txt fi # Activate the virtualenv and upgrade pip From e0fdf56c70f07c51680138b324daccefdf07bca8 Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Sat, 11 Jan 2025 22:34:28 -0500 Subject: [PATCH 2/9] Clarifications on building the container, venv separation and latest tag for DockerHub --- README.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c7eb04a..906aaea 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,24 @@ [ComfyUI](https://github.com/comfyanonymous/ComfyUI/tree/master) is a Stable Diffusion WebUI. With the recent addition of a [Flux example](https://comfyanonymous.github.io/ComfyUI_examples/flux/), I created this container builder to test it. +This container was built to benefit from the process isolation that container build but also to drop the container's ComfyUI privileges to that of a normal user (the container's `comfy` user, which is `sudo` capable). -The container size (over 5GB) contains the required components on an Ubuntu 22.04 image with Nvidia CUDA and CuDNN (the base container is available from Nvidia's DockerHub); we add the requirements components to support an installation of ComfyUI. +The container size (usually over 4GB) contains the required components on an Ubuntu image with Nvidia CUDA and CuDNN (the base container is available from Nvidia's DockerHub); we add the requirements components to support an installation of ComfyUI. -During its first run, it will download ComfyUI from git (into the `run/ComfyUI` folder), create a Python virtual environment (in `run/venv`) for all the Python packages needed by the tool, and install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) into ComfyUI's `custom_nodes` directory. +Multiple images are available. The name of the image contains a tag the reflects its core components. For example: `ubuntu24_cuda12.5.1` is based on an Ubuntu 24.04 with CUDA 12.5.1. +Depending on the version of the Nvidia drivers installed, the Docker container runtime will only support up to a certain version of CUDA. For example, Driver 550 supports up to CUDA 12.4 and therefore will not be able to run the CUDA 12.4.1 or 12.5.1 versions. +Use the `nvidia-smi` command on your system to +For more details on drivers capabilities and how to update those, please see [Setting up NVIDIA docker & podman (Ubuntu 24.04)](https://blg.gkr.one/20240404-u24_nvidia_docker_podman/). + +The `latest` tag will always point to the most up-to-date build (most recent OS+CUDA). +If this version is incompatible with your container runtime, please see the list of alternative builsd from the "Dockerhub" section below. + +During its first run, the container will download ComfyUI from git (into the `run/ComfyUI` folder), create a Python virtual environment (in `run/venv`) for all the Python packages needed by the tool, and install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) into ComfyUI's `custom_nodes` directory. This adds an expected 5GB of content to the installation. Depending on your internet connection, it takes as much time as necessary to complete. + +Given that `venv` (Python virtual environments) might not be compatible from OS+CUDA-version to version and will create a new `venv` when the current one is not for the expected version. +**An installation might end up with multiple `venv`-based directory in the `run` folder, as the tool will rename existing unusable ones as "venv-OS+CUDA" (for example `venv-ubuntu22_cuda12.3.2`). In order to support downgrading if needed, the script will not delete previous `version`, and this is currently left to the end-user to remove if not needed** + You will know the ComfyUI WebUI is running when you check the `docker logs` and see `To see the GUI go to: http://0.0.0.0:8188` **About 10GB of space between the container and the virtual environment installation is needed.** @@ -25,6 +38,8 @@ It is recommended that a container monitoring tool be available to watch the log - [2.3. First time use](#23-first-time-use) - [3. Docker image](#3-docker-image) - [3.1. Building the image](#31-building-the-image) + - [3.1.1. Using the Makefile](#311-using-the-makefile) + - [3.1.2. Using a Dockerfile](#312-using-a-dockerfile) - [3.2. Availability on DockerHub](#32-availability-on-dockerhub) - [3.3. Unraid availability](#33-unraid-availability) - [3.4. Nvidia base container](#34-nvidia-base-container) @@ -33,6 +48,7 @@ It is recommended that a container monitoring tool be available to watch the log - [4.2. FLUX.1\[dev\] example](#42-flux1dev-example) - [5. FAQ](#5-faq) - [5.1. Virtualenv](#51-virtualenv) + - [5.1.1. Multiple virtualenv](#511-multiple-virtualenv) - [5.2. user\_script.bash](#52-user_scriptbash) - [5.3. Available environment variables](#53-available-environment-variables) - [5.3.1. WANTED\_UID and WANTED\_GID](#531-wanted_uid-and-wanted_gid) @@ -45,7 +61,7 @@ It is recommended that a container monitoring tool be available to watch the log # 1. Preamble -This build is made to NOT run as the `root` user, but run within the container as a `comfy` user using the UID/GID requested at `docker run` time (if none are provided, the container will use 1024/1024). +This build is made to NOT run as the `root` user, but run within the container as a `comfy` user using the UID/GID requested at `docker run` time (if none are provided, the container will use `1024`/`1024`). This is done to allow end users to have local directory structures for all the side data (input, output, temp, user), Hugging Face `HF_HOME` if used, and the entire `models`, which are separate from the container and able to be altered by the user. To request a different UID/GID at run time, use the `WANTED_UID` and `WANTED_GID` environment variables when calling the container. @@ -75,7 +91,10 @@ Among the folders that will be created within `run` are `HF, ComfyUI, venv` When starting t the container image executes the `init.bash` script that performs a few operations: - Ensure we can use the `WANTED_UID` and `WANTED_GID` as the `comfy` user (the user set to run the container), - Obtain the latest version of ComfyUI from GitHub if not already present in the mounted `run` folder. -- Create the virtual environment (`venv`) if it does not already exist +- Create the virtual environment (`venv`) if one does not already exist + - if one exists confirm it is the one for this OS+CUDA pair + - if not, rename it and look for a renamed one that would match + - if none is found, create a new one - Activate this virtual environment - Install all the ComfyUI-required Python packages. If those are already present, additional content should not need to be downloaded. - Installing ComfyUI-Manager if it is not present. @@ -160,20 +179,55 @@ Other needed files could be found on [HuggingFace](https://huggingface.co/) or [ ## 3.1. Building the image -Note that a `docker buildx prune -f` might be needed to force a clean build after removing already existing containers. +### 3.1.1. Using the Makefile + -The `comfyui-nvidia-docker` (`latest`) image contains the installation of the core components of ComfyUI from its latest release from GitHub. +Running `make` will show us the different build targets. That list will differ depending on the available `base` files in the `components` directory -Running `make` will show us the different build options; `latest` is the one we want. +For example, you might see: Run: ```bash -make latest +% make +Available comfyui-nvidia-docker docker images to be built (make targets): + ubuntu22_cuda12.3.2 + ubuntu22_cuda12.4.1 + ubuntu24_cuda12.5.1 + +build: builds all +``` + +It is possible to build a specifif target, for example `make ubuntu22_cuda12.3.2` or build all the available containers. + +Running a given target will create a `comfyui-nvidia-docker` `docker buildx`. +As long as none are already present, this will initiate a build with no caching. + +The process will create the `Dockerfile` used within the `Dockerfile` folder. For example. when using `make ubuntu22_cuda12.3.2` a `Dockerfile/Dockerfile-ubuntu22_cuda12.3.2` file is created that will contain the steps used to build the local `comfyui-nvidia-docker:ubuntu22_cuda12.3.2` Docker image. + +### 3.1.2. Using a Dockerfile + +It is also possible to use one of the generated `Dockerfile` to build a specific image. +After selecting the image to build from the `OS+CUDA` name within the `Dockerfile` folder, proceed with a `docker build` command in the directory where this `README.md` is located. +For example to build the `ubuntu24_cuda12.5.1` container, run: + +```bash +docker build --tag comfyui-nvidia-docker:ubuntu24_cuda12.5.1 -f Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 . ``` +Upon a succesful build completion, we will have a newly created local `comfyui-nvidia-docker:ubuntu24_cuda12.5.1` Docker image. + ## 3.2. Availability on DockerHub -Builds are available on DockerHub at [mmartial/comfyui-nvidia-docker](https://hub.docker.com/r/mmartial/comfyui-nvidia-docker), built from this repository's `Dockerfile`. +Builds are available on DockerHub at [mmartial/comfyui-nvidia-docker](https://hub.docker.com/r/mmartial/comfyui-nvidia-docker), built from this repository's `Dockerfile`(s). + +The following table shows the list of available versions on DockerHub. As discused before, make sure your NVIDIA container runtime supports the proposed CUDA version. This is particularily important if you use the `latest` tag, as it is expected to refer to the most recent OS+CUDA release. + +| tag | aka | +| --- | --- | +| ubuntu22_cuda12.3.2-latest | | +| ubuntu22_cuda12.4.1-latest | | +| ubuntu24_cuda12.5.1-latest | latest | + ## 3.3. Unraid availability @@ -215,6 +269,20 @@ This allows for the installation of Python packages using `pip3 install`. After running `docker exec -t comfy-nvidia /bin/bash` from the provided `bash`, activate the `venv` with `source /comfy/mnt/venv/bin/activate`. From this `bash` prompt, you can now run `pip3 freeze` or other `pip3` commands such as `pip3 install civitai` +### 5.1.1. Multiple virtualenv + +Because a `venv` is tied to a OS+CUDA version, the tool attempts to create some internal logic so that the `venv` folder matches the OS+CUDA of the started container. +**Starting two `comfyui-nvidia-docker` containers with different OS+CUDA tags at the same time is likely to cause some issues** + +For illustration, let's say we last ran `ubuntu22_cuda12.3.1`, exited the container and now attempt to run `ubuntu24_cuda12.5.1`. The script initialization is as follows: +- check for an existing `venv`; there is one +- check that this `venv` is for `ubuntu24_cuda12.5.1`: it is not, it is for `ubuntu22_cuda12.3.1` +- move `venv` to `venv-ubuntu22_cuda12.3.1` +- check if there is a `venv-ubuntu24_cuda12.5.1` to renamed as `venv` if present: there is not +- the script continues as if there was no `venv` and a new one for `ubuntu24_cuda12.5.1` is created + +Because of this, it is possible to have multiple `venv`-based folders in the "run" folder. + ## 5.2. user_script.bash The `run/user_script.bash` user script can perform additional operations. @@ -292,9 +360,9 @@ It is also possible to use the environment variables in combination with the `us ```bash #!/bin/bash -#echo "== Adding system package" +#echo "== Update installed packages" DEBIAN_FRONTEND=noninteractive sudo apt-get update -DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libgl1 libglib2.0-0 +DEBIAN_FRONTEND=noninteractive sudo apt-get upgrade -y # Exit with an "okay" status to allow the init script to run the regular ComfyUI command exit 0 From a5123040f0c0f4aebae7c830acf67b9fc75759a2 Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:58:50 -0500 Subject: [PATCH 3/9] (preliminary) attempt to use only "present" (existing) images --- Makefile | 125 +++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index 95f5f6f..7338b44 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ DOCKERFILE_DIR=Dockerfile DOCKER_ALL=$(shell ls -1 ${COMPONENTS_DIR}/base-* | perl -pe 's%^.+/base-%%' | sort) all: + @if [ `echo ${DOCKER_ALL} | wc -w` -eq 0 ]; then echo "No images candidates to build"; exit 1; fi @echo "Available ${COMFYUI_CONTAINER_NAME} ${DOCKER_CMD} images to be built (make targets):" @echo -n " "; echo ${DOCKER_ALL} | sed -e 's/ /\n /g' @echo "" @@ -33,9 +34,8 @@ ${DOCKER_ALL}: ${DOCKERFILE_DIR} @cat ${COMPONENTS_DIR}/part1-common >> ${DOCKERFILE_DIR}/Dockerfile-$@ @$(eval VAR_NT="${COMFYUI_CONTAINER_NAME}-$@") @echo "-- Docker command to be run:" - @${eval CND_BUILDX="${COMFYUI_CONTAINER_NAME}"} - @echo "docker buildx ls | grep -q ${CND_BUILDX} && echo \"builder already exists -- to delete it, use: docker buildx rm ${CND_BUILDX}\" || docker buildx create --name ${CND_BUILDX}" > ${VAR_NT}.cmd - @echo "docker buildx use ${CND_BUILDX} || exit 1" >> ${VAR_NT}.cmd + @echo "docker buildx ls | grep -q ${COMFYUI_CONTAINER_NAME} && echo \"builder already exists -- to delete it, use: docker buildx rm ${COMFYUI_CONTAINER_NAME}\" || docker buildx create --name ${COMFYUI_CONTAINER_NAME}" > ${VAR_NT}.cmd + @echo "docker buildx use ${COMFYUI_CONTAINER_NAME} || exit 1" >> ${VAR_NT}.cmd @echo "BUILDX_EXPERIMENTAL=1 ${DOCKER_PRE} docker buildx debug --on=error build --progress plain --platform linux/amd64 ${DOCKER_BUILD_ARGS} \\" >> ${VAR_NT}.cmd @echo " --build-arg BUILD_DATE=\"${BUILD_DATE}\" \\" >> ${VAR_NT}.cmd @echo " --build-arg BUILD_BASE=\"$@\" \\" >> ${VAR_NT}.cmd @@ -53,82 +53,45 @@ ${DOCKER_ALL}: ${DOCKERFILE_DIR} @mv ${VAR_NT}.log.temp ${VAR_NT}.log @rm -f ./${VAR_NT}.cmd -# -#BUILD_BASE=ubuntu22_cuda12.3 -# -#BUILD_TAG=${BUILD_BASE}-latest -#NAMED_BUILD=${COMFYUI_CONTAINER_NAME}:${BUILD_TAG} -#NAMED_BUILD_LATEST=${COMFYUI_CONTAINER_NAME}:latest -# -#DOCKERFILE=Dockerfile -#DOCKER_PRE="NVIDIA_VISIBLE_DEVICES=all" -# -# -#DOCKER_BUILD_ARGS= -##DOCKER_BUILD_ARGS="--no-cache" -# -## Set to False to make it less verbose -#VERBOSE_PRINT=True -# -###### -# -#all: -# @echo "** Available Docker images to be built (make targets):" -# @echo "latest: builds ${NAMED_BUILD} and tags it as ${NAMED_BUILD_LATEST}" -# @echo "" -# @echo "build: builds latest" -# -###### latest -# -#build: -# @make latest -# -# -#latest: -# @VAR_NT=${COMFYUI_CONTAINER_NAME}-${BUILD_TAG} USED_BUILD=${NAMED_BUILD} USED_BUILD_LATEST=${NAMED_BUILD_LATEST} make build_main_actual -# -# -#build_main_actual: -# @echo "== [${USED_BUILD}] ==" -# @echo "-- Docker command to be run:" -# @echo "BUILDX_EXPERIMENTAL=1 ${DOCKER_PRE} docker buildx debug --on=error build --progress plain --platform linux/amd64 ${DOCKER_BUILD_ARGS} \\" > ${VAR_NT}.cmd -# @echo " --build-arg DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd -# @echo " --build-arg BASE_DOCKER_FROM=\"${DOCKER_FROM}\" \\" >> ${VAR_NT}.cmd -# @echo " --build-arg BUILD_DATE=\"${BUILD_DATE}\" \\" >> ${VAR_NT}.cmd -# @echo " --build-arg BUILD_BASE=\"${BUILD_BASE}\" \\" >> ${VAR_NT}.cmd -# @echo " --tag=\"${USED_BUILD}\" \\" >> ${VAR_NT}.cmd -# @echo " -f ${DOCKERFILE} \\" >> ${VAR_NT}.cmd -# @echo " ." >> ${VAR_NT}.cmd -# -# @cat ${VAR_NT}.cmd | tee ${VAR_NT}.log.temp -# @chmod +x ./${VAR_NT}.cmd -# @script -a -e -c ./${VAR_NT}.cmd ${VAR_NT}.log.temp; exit "$${PIPESTATUS[0]}" -# -# @mv ${VAR_NT}.log.temp ${VAR_NT}.log -# @rm -f ./${VAR_NT}.cmd -# -# @${DOCKER_CMD} tag ${USED_BUILD} ${USED_BUILD_LATEST} -# -# ###### clean -# -#docker_rmi: -# docker rmi --force ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} -# -# + +docker_tag_list: + @echo "Docker images tagged:" + @${DOCKER_CMD} images --filter "label=comfyui-nvidia-docker-build" + +docker_buildx_rm: + @docker buildx rm ${COMFYUI_CONTAINER_NAME} + +# Get the list of all existing Docker images +DOCKERHUB_REPO="mmartial" +DOCKER_PRESENT=$(shell for i in ${DOCKER_ALL}; do image="${COMFYUI_CONTAINER_NAME}:$$i"; if docker images --format "{{.Repository}}:{{.Tag}}" | grep -v ${DOCKERHUB_REPO} | grep -q $$image; then echo $$image; fi; done) + +docker_rmi: + @echo -n "== Images to delete: " + @echo ${DOCKER_PRESENT} | wc -w + @if [ `echo ${DOCKER_PRESENT} | wc -w` -eq 0 ]; then echo "No images to delete"; exit 1; fi + @echo ${DOCKER_PRESENT} | sed -e 's/ /\n/g' + @echo "" + @echo "Press Ctl+c within 5 seconds to cancel" + @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" + @for i in ${DOCKER_PRESENT}; do docker rmi $$i; done + + ############################################### For maintainer only -###### push -#DOCKERHUB_REPO="mmartial" -# -#docker_tag: -# @make latest -# @${DOCKER_CMD} tag ${NAMED_BUILD} ${DOCKERHUB_REPO}/${NAMED_BUILD} -# @${DOCKER_CMD} tag ${NAMED_BUILD_LATEST} ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} -# @make docker_tag_list -# -#docker_tag_list: -# @echo "Docker images tagged:" -# @${DOCKER_CMD} images --filter "label=comfyui-nvidia-docker-build" +###### push -- will only proceed with existing ("present") images + +LATEST_ENTRY=$(shell echo ${DOCKER_ALL} | sed -e 's/ /\n/g' | tail -1) +LATEST_CANDIDATE=$(shell echo ${COMFYUI_CONTAINER_NAME}:${LATEST_ENTRY}) + +docker_tag: + @if [ `echo ${DOCKER_PRESENT} | wc -w` -eq 0 ]; then echo "No images to tag"; exit 1; fi + @echo "== About to tag:" + @for i in ${DOCKER_PRESENT}; do image_out1="${DOCKERHUB_REPO}/$$i-${BUILD_DATE}"; image_out2="${DOCKERHUB_REPO}/$$i-latest"; echo "$$i -> $$image_out1"; echo "$$i -> $$image_out2"; done + @if echo ${DOCKER_PRESENT} | grep -q ${LATEST_CANDIDATE}; then image_out="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; echo "${LATEST_CANDIDATE} -> $$image_out"; else echo "Unable to find latest candidate: ${LATEST_CANDIDATE}"; fi + @echo "" + @echo "tagging for hub.docker.com upload -- Press Ctl+c within 5 seconds to cancel" + @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" + # #docker_push: # @make docker_tag @@ -136,4 +99,10 @@ ${DOCKER_ALL}: ${DOCKERFILE_DIR} # @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" # @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD} # @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} -# \ No newline at end of file +# +#docker_rmi_all: +# @for i in ${DOCKER_ALL}; do image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:${i}-${BUILD_DATE}"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi; done +# @for i in ${DOCKER_ALL}; do image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:${i}-latest"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi; done +# @image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi +# @make docker_rmi +# @make docker_tag_list \ No newline at end of file From 92ff1b2388f4d14098c4ba5d1f32afc3a57f14af Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Sun, 12 Jan 2025 17:54:49 -0500 Subject: [PATCH 4/9] Attempt to work with "Import Failed" using cm-cli --- README.md | 10 ++++++++++ assets/ImportFailed-TryFix.png | Bin 0 -> 72955 bytes init.bash | 17 ++++++++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 assets/ImportFailed-TryFix.png diff --git a/README.md b/README.md index 906aaea..0e36e47 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This adds an expected 5GB of content to the installation. Depending on your inte Given that `venv` (Python virtual environments) might not be compatible from OS+CUDA-version to version and will create a new `venv` when the current one is not for the expected version. **An installation might end up with multiple `venv`-based directory in the `run` folder, as the tool will rename existing unusable ones as "venv-OS+CUDA" (for example `venv-ubuntu22_cuda12.3.2`). In order to support downgrading if needed, the script will not delete previous `version`, and this is currently left to the end-user to remove if not needed** +Using alernate `venv` means that some installed custom nodes might have an `import dailed` error. We are attempting to make use of [`cm-cli`](https://github.com/ltdrdata/ComfyUI-Manager/blob/main/docs/en/cm-cli.md) before starting ComfyUI. If that fails, start the `Manager -> Custom Nodes Manager`, `Filter` by `Import Failed` and use the `Try fix` button as this will download required pacakges and install those in the used `venv`. A `Restart` and UI reload will be required but this ought to fix issues with the nodes. You will know the ComfyUI WebUI is running when you check the `docker logs` and see `To see the GUI go to: http://0.0.0.0:8188` @@ -49,6 +50,7 @@ It is recommended that a container monitoring tool be available to watch the log - [5. FAQ](#5-faq) - [5.1. Virtualenv](#51-virtualenv) - [5.1.1. Multiple virtualenv](#511-multiple-virtualenv) + - [5.1.2. Fixing Failed Nodes](#512-fixing-failed-nodes) - [5.2. user\_script.bash](#52-user_scriptbash) - [5.3. Available environment variables](#53-available-environment-variables) - [5.3.1. WANTED\_UID and WANTED\_GID](#531-wanted_uid-and-wanted_gid) @@ -283,6 +285,14 @@ For illustration, let's say we last ran `ubuntu22_cuda12.3.1`, exited the contai Because of this, it is possible to have multiple `venv`-based folders in the "run" folder. +### 5.1.2. Fixing Failed Nodes + +A side effect of the multiple virtual environment integration is that some installed custom nodes might have an `import failed` error when switching from one OS+CUDA-version to another. +When the container is initialized we run `cm-cli.py fix all` to attempt to fix for this. +If this does not resolve the issue, start the `Manager -> Custom Nodes Manager`, `Filter` by `Import Failed` and use the `Try fix` button as this will download required pacakges and install those in the used `venv`. A `Restart` and UI reload will be required but this ought to fix issues with the nodes. + +![Import Failed: Try Fix](./assets/ImportFailed-TryFix.png) + ## 5.2. user_script.bash The `run/user_script.bash` user script can perform additional operations. diff --git a/assets/ImportFailed-TryFix.png b/assets/ImportFailed-TryFix.png new file mode 100644 index 0000000000000000000000000000000000000000..196e86ec0e7afe8bf09e990e523a7b07074a0389 GIT binary patch literal 72955 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 Q0r(>;sr0Bo+}Q7b0f$ElcK`qY literal 0 HcmV?d00001 diff --git a/init.bash b/init.bash index 0e1dcd2..4418b4c 100644 --- a/init.bash +++ b/init.bash @@ -86,7 +86,7 @@ it=/etc/build_base.txt if [ ! -f $it ]; then error_exit "$it missing, exiting"; fi BUILD_BASE=`cat $it` BUILD_BASE_FILE=$it -BUILD_BASE_SPECIAL="ubuntu22_cuda12.3" # this is a special value: when this feature was introduced, will be used to mark exisitng venv if the marker is not present +BUILD_BASE_SPECIAL="ubuntu22_cuda12.3.2" # this is a special value: when this feature was introduced, will be used to mark exisitng venv if the marker is not present echo "-- BUILD_BASE: \"${BUILD_BASE}\"" if test -z ${BUILD_BASE}; then error_exit "Empty BUILD_BASE variable"; fi @@ -223,14 +223,15 @@ if [ ! -d ComfyUI-Manager ]; then git clone https://github.com/ltdrdata/ComfyUI-Manager.git || error_exit "ComfyUI-Manager clone failed" fi if [ ! -d ComfyUI-Manager ]; then error_exit "ComfyUI-Manager not found"; fi +pip3 install --trusted-host pypi.org --trusted-host files.pythonhosted.org -r ${COMFYUI_PATH}/custom_nodes/ComfyUI-Manager/requirements.txt || echo "ComfyUI-Manager CLI requirements install/upgrade failed" # Lower security_level for ComfyUI-Manager to allow access from outside the container # This is needed to allow the WebUI to be served on 0.0.0.0 ie all interfaces and not just localhost (which would be limited to within the container) # Please see https://github.com/ltdrdata/ComfyUI-Manager?tab=readme-ov-file#security-policy for more details # # recent releases of ComfyUI-Manager have a config.ini file in the user folder, if this is not present, we expect it in the default folder -cm_conf_user=/comfy/mnt/ComfyUI/user/default/ComfyUI-Manager/config.ini -cm_conf=/comfy/mnt/ComfyUI/custom_nodes/ComfyUI-Manager/config.ini +cm_conf_user=${COMFYUI_PATH}/user/default/ComfyUI-Manager/config.ini +cm_conf=${COMFYUI_PATH}/custom_nodes/ComfyUI-Manager/config.ini if [ -f $cm_conf_user ]; then cm_conf=$cm_conf_user; fi if [ ! -f $cm_conf ]; then echo "== ComfyUI-Manager $cm_conf file missing, script potentially never run before. You will need to run ComfyUI-Manager a first time for the configuration file to be generated, we can not attempt to update its security level yet -- if this keeps occurring, please let the developer know so he can investigate. Thank you" @@ -241,6 +242,16 @@ else grep security_level $cm_conf fi +# Attempt to use ComfyUI Manager CLI to fix all installed nodes -- This must be done within the activated virtualenv +cm_cli=${COMFYUI_PATH}/custom_nodes/ComfyUI-Manager/cm-cli.py +if [ -f $cm_cli ]; then + echo "== Running ComfyUI-Manager CLI to fix installed custom nodes" + python3 $cm_cli fix all || echo "ComfyUI-Manager CLI failed -- please run it manually from the WebUI in case a custom node is mising some package" +else + echo "== ComfyUI-Manager CLI not found, skipping" +fi + +# Final steps before running ComfyUI cd ${COMFYUI_PATH} echo -n "== Container directory: "; pwd From eef25ae23d70d1de8fd2962706096b121ce4ba88 Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:05:47 -0500 Subject: [PATCH 5/9] Avoid re-runing cm-cli when re-running with the same venv --- init.bash | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/init.bash b/init.bash index 4418b4c..4143428 100644 --- a/init.bash +++ b/init.bash @@ -161,6 +161,7 @@ if [ -d "venv" ]; then it=venv/.build_base.txt; if [ ! -f $it ]; then echo $BUILD_BASE_SPECIAL > $it; fi fi +SWITCHED_VENV=True # this is a marker to indicate that we have switched to a different venv, which is set unless we re-use the same venv as before (see below) # Check for an existing venv; if present, is it the proper one -- ie does its .build_base.txt match the container's BUILD_BASE_FILE? if [ -d venv ]; then it=venv/.build_base.txt @@ -168,6 +169,7 @@ if [ -d venv ]; then if cmp --silent $it $BUILD_BASE_FILE; then echo "== venv is for this BUILD_BASE (${BUILD_BASE})" + SWITCHED_VENV=False else echo "== venv ($venv_bb) is not for this BUILD_BASE (${BUILD_BASE}), renaming it and seeing if a valid one is present" mv venv venv-${venv_bb} || error_exit "Failed to rename venv to venv-${venv_bb}" @@ -243,12 +245,17 @@ else fi # Attempt to use ComfyUI Manager CLI to fix all installed nodes -- This must be done within the activated virtualenv -cm_cli=${COMFYUI_PATH}/custom_nodes/ComfyUI-Manager/cm-cli.py -if [ -f $cm_cli ]; then - echo "== Running ComfyUI-Manager CLI to fix installed custom nodes" - python3 $cm_cli fix all || echo "ComfyUI-Manager CLI failed -- please run it manually from the WebUI in case a custom node is mising some package" -else - echo "== ComfyUI-Manager CLI not found, skipping" +if [ "A${SWITCHED_VENV}" == "AFalse" ]; then + echo "== Skipping ComfyUI-Manager CLI fix as we are re-using the same venv as the last execution" + echo " -- If you are experiencing issues with custom nodes, use 'Manager -> Custom Nodes Manager -> Filter: Import Failed -> Try Fix' from the WebUI" +else + cm_cli=${COMFYUI_PATH}/custom_nodes/ComfyUI-Manager/cm-cli.py + if [ -f $cm_cli ]; then + echo "== Running ComfyUI-Manager CLI to fix installed custom nodes" + python3 $cm_cli fix all || echo "ComfyUI-Manager CLI failed -- in case of issue with custom nodes: use 'Manager -> Custom Nodes Manager -> Filter: Import Failed -> Try Fix' from the WebUI" + else + echo "== ComfyUI-Manager CLI not found, skipping" + fi fi # Final steps before running ComfyUI From f60916ace472b0b4c0962665aa9bdd3668cb927f Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:05:20 -0500 Subject: [PATCH 6/9] Added tag and push logic --- Makefile | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 7338b44..4b4052c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ DOCKER_PRE="NVIDIA_VISIBLE_DEVICES=all" DOCKER_BUILD_ARGS= ##DOCKER_BUILD_ARGS="--no-cache" -BUILD_DATE=$(shell printf '%(%Y%m%d)T' -1) +#BUILD_DATE=$(shell printf '%(%Y%m%d)T' -1) +BUILD_DATE=202501wip COMFYUI_CONTAINER_NAME=comfyui-nvidia-docker @@ -56,7 +57,6 @@ ${DOCKER_ALL}: ${DOCKERFILE_DIR} ###### clean docker_tag_list: - @echo "Docker images tagged:" @${DOCKER_CMD} images --filter "label=comfyui-nvidia-docker-build" docker_buildx_rm: @@ -75,6 +75,8 @@ docker_rmi: @echo "Press Ctl+c within 5 seconds to cancel" @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" @for i in ${DOCKER_PRESENT}; do docker rmi $$i; done + @echo ""; echo " ** Remaining image with the build label:" + @make docker_tag_list ############################################### For maintainer only @@ -86,23 +88,35 @@ LATEST_CANDIDATE=$(shell echo ${COMFYUI_CONTAINER_NAME}:${LATEST_ENTRY}) docker_tag: @if [ `echo ${DOCKER_PRESENT} | wc -w` -eq 0 ]; then echo "No images to tag"; exit 1; fi @echo "== About to tag:" - @for i in ${DOCKER_PRESENT}; do image_out1="${DOCKERHUB_REPO}/$$i-${BUILD_DATE}"; image_out2="${DOCKERHUB_REPO}/$$i-latest"; echo "$$i -> $$image_out1"; echo "$$i -> $$image_out2"; done - @if echo ${DOCKER_PRESENT} | grep -q ${LATEST_CANDIDATE}; then image_out="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; echo "${LATEST_CANDIDATE} -> $$image_out"; else echo "Unable to find latest candidate: ${LATEST_CANDIDATE}"; fi + @for i in ${DOCKER_PRESENT}; do image_out1="${DOCKERHUB_REPO}/$$i-${BUILD_DATE}"; image_out2="${DOCKERHUB_REPO}/$$i-latest"; echo " ++ $$i -> $$image_out1"; echo " ++ $$i -> $$image_out2"; done + @if echo ${DOCKER_PRESENT} | grep -q ${LATEST_CANDIDATE}; then image_out="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; echo " ++ ${LATEST_CANDIDATE} -> $$image_out"; else echo " -- Unable to find latest candidate: ${LATEST_CANDIDATE}"; fi @echo "" @echo "tagging for hub.docker.com upload -- Press Ctl+c within 5 seconds to cancel" @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" + @for i in ${DOCKER_PRESENT}; do image_out1="${DOCKERHUB_REPO}/$$i-${BUILD_DATE}"; image_out2="${DOCKERHUB_REPO}/$$i-latest"; docker tag $$i $$image_out1; docker tag $$i $$image_out2; done + @if echo ${DOCKER_PRESENT} | grep -q ${LATEST_CANDIDATE}; then image_out="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; docker tag ${LATEST_CANDIDATE} $$image_out; fi -# -#docker_push: -# @make docker_tag -# @echo "hub.docker.com upload -- Press Ctl+c within 5 seconds to cancel -- will only work for maintainers" -# @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" -# @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD} -# @${DOCKER_CMD} push ${DOCKERHUB_REPO}/${NAMED_BUILD_LATEST} -# -#docker_rmi_all: -# @for i in ${DOCKER_ALL}; do image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:${i}-${BUILD_DATE}"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi; done -# @for i in ${DOCKER_ALL}; do image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:${i}-latest"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi; done -# @image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; echo "** Checking: ${image}"; if docker images | grep -q ${image}; then docker rmi ${image}; fi -# @make docker_rmi -# @make docker_tag_list \ No newline at end of file +DOCKERHUB_READY=$(shell for i in ${DOCKER_ALL}; do image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:$$i"; image1=$$image-${BUILD_DATE}; image2=$$image-latest; if docker images --format "{{.Repository}}:{{.Tag}}" | grep -q $$image1; then echo $$image1; fi; if docker images --format "{{.Repository}}:{{.Tag}}" | grep -q $$image2; then echo $$image2; fi; done) +DOCKERHUB_READY_LATEST=$(shell image="${DOCKERHUB_REPO}/${COMFYUI_CONTAINER_NAME}:latest"; if docker images --format "{{.Repository}}:{{.Tag}}" | grep -q $$image; then echo $$image; else echo ""; fi) + + +docker_push: + @if [ `echo ${DOCKERHUB_READY} | wc -w` -eq 0 ]; then echo "No images to push"; exit 1; fi + @echo "== About to push:" + @for i in ${DOCKERHUB_READY} ${DOCKERHUB_READY_LATEST}; do echo " ++ $$i"; done + @echo "pushing to hub.docker.com -- Press Ctl+c within 5 seconds to cancel" + @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" + @for i in ${DOCKERHUB_READY} ${DOCKERHUB_READY_LATEST}; do docker push $$i; done + + +docker_rmi_hub: + @echo ""; echo " ** Potential images with the build label:" + @make docker_tag_list + @if [ `echo ${DOCKERHUB_READY} ${DOCKERHUB_READY_LATEST} | wc -w` -eq 0 ]; then echo "No expected images to delete"; exit 1; fi + @echo "== About to delete:" + @for i in ${DOCKERHUB_READY} ${DOCKERHUB_READY_LATEST}; do echo " -- $$i"; done + @echo "deleting -- Press Ctl+c within 5 seconds to cancel" + @for i in 5 4 3 2 1; do echo -n "$$i "; sleep 1; done; echo "" + @for i in ${DOCKERHUB_READY} ${DOCKERHUB_READY_LATEST}; do docker rmi $$i; done + @echo ""; echo " ** Remaining images with the build label:" + @make docker_tag_list From 9bbe11f3b2f27d12b685e3fda8f54fc787d5dcc5 Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:08:37 -0500 Subject: [PATCH 7/9] Added easier to reach details on available tags --- README.md | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0e36e47..439305e 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,20 @@ The container size (usually over 4GB) contains the required components on an Ubu Multiple images are available. The name of the image contains a tag the reflects its core components. For example: `ubuntu24_cuda12.5.1` is based on an Ubuntu 24.04 with CUDA 12.5.1. Depending on the version of the Nvidia drivers installed, the Docker container runtime will only support up to a certain version of CUDA. For example, Driver 550 supports up to CUDA 12.4 and therefore will not be able to run the CUDA 12.4.1 or 12.5.1 versions. -Use the `nvidia-smi` command on your system to +Use the `nvidia-smi` command on your system to obtain the `CUDA Version:` entry in the produced table's header. For more details on drivers capabilities and how to update those, please see [Setting up NVIDIA docker & podman (Ubuntu 24.04)](https://blg.gkr.one/20240404-u24_nvidia_docker_podman/). -The `latest` tag will always point to the most up-to-date build (most recent OS+CUDA). -If this version is incompatible with your container runtime, please see the list of alternative builsd from the "Dockerhub" section below. +The `latest` tag will always point to the most up-to-date build (i.e., the most recent OS+CUDA). +If this version is incompatible with your container runtime, please see the list of alternative builds. -During its first run, the container will download ComfyUI from git (into the `run/ComfyUI` folder), create a Python virtual environment (in `run/venv`) for all the Python packages needed by the tool, and install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) into ComfyUI's `custom_nodes` directory. -This adds an expected 5GB of content to the installation. Depending on your internet connection, it takes as much time as necessary to complete. +| tag | aka | +| --- | --- | +| ubuntu22_cuda12.3.2-latest | | +| ubuntu22_cuda12.4.1-latest | | +| ubuntu24_cuda12.5.1-latest | latest | + +During its first run, the container will download ComfyUI from `git` (into the `run/ComfyUI` folder), create a Python virtual environment (in `run/venv`) for all the Python packages needed by the tool, and install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) into ComfyUI's `custom_nodes` directory. +This adds about 5GB of content to the installation. Download time depends on your internet connection. Given that `venv` (Python virtual environments) might not be compatible from OS+CUDA-version to version and will create a new `venv` when the current one is not for the expected version. **An installation might end up with multiple `venv`-based directory in the `run` folder, as the tool will rename existing unusable ones as "venv-OS+CUDA" (for example `venv-ubuntu22_cuda12.3.2`). In order to support downgrading if needed, the script will not delete previous `version`, and this is currently left to the end-user to remove if not needed** @@ -50,7 +56,7 @@ It is recommended that a container monitoring tool be available to watch the log - [5. FAQ](#5-faq) - [5.1. Virtualenv](#51-virtualenv) - [5.1.1. Multiple virtualenv](#511-multiple-virtualenv) - - [5.1.2. Fixing Failed Nodes](#512-fixing-failed-nodes) + - [5.1.2. Fixing Failed Custom Nodes](#512-fixing-failed-custom-nodes) - [5.2. user\_script.bash](#52-user_scriptbash) - [5.3. Available environment variables](#53-available-environment-variables) - [5.3.1. WANTED\_UID and WANTED\_GID](#531-wanted_uid-and-wanted_gid) @@ -88,9 +94,12 @@ Among the folders that will be created within `run` are `HF, ComfyUI, venv` - `custom_nodes` for additional support nodes, for example ComfyUI-Manager, - `models` and all its sub-directories is where `checkpoints`, `clip`, `loras`, `unet`, etc have to be placed. - `input` and `output` are where input images will be placed and generated images will end up. + - `user` is where the user's customizations, saved `workflows` (and ComfyUI Manager's configuration) are stored. - `venv` is the virtual environment where all the required Python packages for ComfyUI and other additions will be placed. A default ComfyUI package installation requires about 5GB of additional installation in addition to the container itself; those packages will be in this `venv` folder. -When starting t the container image executes the `init.bash` script that performs a few operations: +**Currently, it is not recommended to volume map folders within the `ComfyUI` folder**. Doing so is likely to prevent proper installation (during the first run) or update, as any volume mapping (`docker ... -v` or `- local_path:container_path` for compose) creates those directories within a directory structure that is not suppoeed to exist at first run. + +When starting, the container image executes the `init.bash` script that performs a few operations: - Ensure we can use the `WANTED_UID` and `WANTED_GID` as the `comfy` user (the user set to run the container), - Obtain the latest version of ComfyUI from GitHub if not already present in the mounted `run` folder. - Create the virtual environment (`venv`) if one does not already exist @@ -222,14 +231,7 @@ Upon a succesful build completion, we will have a newly created local `comfyui- Builds are available on DockerHub at [mmartial/comfyui-nvidia-docker](https://hub.docker.com/r/mmartial/comfyui-nvidia-docker), built from this repository's `Dockerfile`(s). -The following table shows the list of available versions on DockerHub. As discused before, make sure your NVIDIA container runtime supports the proposed CUDA version. This is particularily important if you use the `latest` tag, as it is expected to refer to the most recent OS+CUDA release. - -| tag | aka | -| --- | --- | -| ubuntu22_cuda12.3.2-latest | | -| ubuntu22_cuda12.4.1-latest | | -| ubuntu24_cuda12.5.1-latest | latest | - +The table at the top of this document shows the list of available versions on DockerHub. Make sure your NVIDIA container runtime supports the proposed CUDA version. This is particularily important if you use the `latest` tag, as it is expected to refer to the most recent OS+CUDA release. ## 3.3. Unraid availability @@ -285,7 +287,7 @@ For illustration, let's say we last ran `ubuntu22_cuda12.3.1`, exited the contai Because of this, it is possible to have multiple `venv`-based folders in the "run" folder. -### 5.1.2. Fixing Failed Nodes +### 5.1.2. Fixing Failed Custom Nodes A side effect of the multiple virtual environment integration is that some installed custom nodes might have an `import failed` error when switching from one OS+CUDA-version to another. When the container is initialized we run `cm-cli.py fix all` to attempt to fix for this. @@ -413,8 +415,10 @@ See [extras/FAQ.md] for additional FAQ topics, among which: # 6. Troubleshooting The `venv` in the "run" directory contains all the required Python packages used by the tool. -In case of an issue, it is recommended that you terminate the container, delete the `venv` directory, and restart the container. -The virtual environment will be recreated; any `custom_scripts` should re-install their requirements. +In case of an issue, it is recommended that you terminate the container, delete (or rename) the `venv` directory, and restart the container. +The virtual environment will be recreated; any `custom_scripts` should re-install their requirements; please see the "Fixing Failed Custom Nodes" section for additional details. + +It is also possible to rename the entire "run" directory for get a clean installation of ComfyUI and its virtual environment. This method is preferred --compared to deleting the "run" directory-- as it will allow us to copy the content of the various downloaded `ComfyUI/models`, `ComfyUI/custom_nodes`, generated `ComfyUI/outputs`, `ComfyUI/user`, added `ComfyUI/inputs` and other folder present within the old "run" directory. # 7. Changelog From 3c5c1e08029ff5cca35fb37621c9ce0e00d08f9a Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:50:58 -0500 Subject: [PATCH 8/9] adding ffmpeg --- Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 | 3 +++ Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 | 3 +++ Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 | 3 +++ components/part1-common | 3 +++ 4 files changed, 12 insertions(+) diff --git a/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 b/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 index 4d02fac..552b42c 100644 --- a/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 +++ b/Dockerfile/Dockerfile-ubuntu22_cuda12.3.2 @@ -51,8 +51,11 @@ RUN apt-get update -y --fix-missing \ python3-venv \ git \ sudo \ + # Adding libGL (used by a few common nodes) libgl1 \ libglib2.0-0 \ + # Adding FFMPEG (for video generation workflow) + ffmpeg \ && apt-get clean ENV BUILD_FILE="/etc/image_base.txt" diff --git a/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 b/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 index 43bf068..e3443a1 100644 --- a/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 +++ b/Dockerfile/Dockerfile-ubuntu22_cuda12.4.1 @@ -49,8 +49,11 @@ RUN apt-get update -y --fix-missing \ python3-venv \ git \ sudo \ + # Adding libGL (used by a few common nodes) libgl1 \ libglib2.0-0 \ + # Adding FFMPEG (for video generation workflow) + ffmpeg \ && apt-get clean ENV BUILD_FILE="/etc/image_base.txt" diff --git a/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 b/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 index aa3cf99..0c0b3a3 100644 --- a/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 +++ b/Dockerfile/Dockerfile-ubuntu24_cuda12.5.1 @@ -48,8 +48,11 @@ RUN apt-get update -y --fix-missing \ python3-venv \ git \ sudo \ + # Adding libGL (used by a few common nodes) libgl1 \ libglib2.0-0 \ + # Adding FFMPEG (for video generation workflow) + ffmpeg \ && apt-get clean ENV BUILD_FILE="/etc/image_base.txt" diff --git a/components/part1-common b/components/part1-common index 20f8352..9e9022e 100644 --- a/components/part1-common +++ b/components/part1-common @@ -32,8 +32,11 @@ RUN apt-get update -y --fix-missing \ python3-venv \ git \ sudo \ + # Adding libGL (used by a few common nodes) libgl1 \ libglib2.0-0 \ + # Adding FFMPEG (for video generation workflow) + ffmpeg \ && apt-get clean ENV BUILD_FILE="/etc/image_base.txt" From 16550e3847e535dc4ea6b43e9ab002e9e49f4acd Mon Sep 17 00:00:00 2001 From: Martial Michel <7586284+mmartial@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:51:36 -0500 Subject: [PATCH 9/9] 20250116 release --- Makefile | 2 +- README.md | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 4b4052c..bb8a88b 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ DOCKER_BUILD_ARGS= ##DOCKER_BUILD_ARGS="--no-cache" #BUILD_DATE=$(shell printf '%(%Y%m%d)T' -1) -BUILD_DATE=202501wip +BUILD_DATE=20250116 COMFYUI_CONTAINER_NAME=comfyui-nvidia-docker diff --git a/README.md b/README.md index 439305e..418e16d 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ It is recommended that a container monitoring tool be available to watch the log - [5.3.2. COMFY\_CMDLINE\_BASE and COMFY\_CMDLINE\_XTRA](#532-comfy_cmdline_base-and-comfy_cmdline_xtra) - [5.3.3. SECURITY\_LEVEL](#533-security_level) - [5.4. ComfyUI Manager \& Security levels](#54-comfyui-manager--security-levels) - - [5.5. Additional FAQ](#55-additional-faq) + - [5.5. Shell within the Docker image](#55-shell-within-the-docker-image) + - [5.6. Additional FAQ](#56-additional-faq) - [6. Troubleshooting](#6-troubleshooting) - [7. Changelog](#7-changelog) @@ -405,7 +406,11 @@ Note that if this is the first time starting the container, the file will not ye To use `cm-cli`, from the virtualenv, use: `python3 /comfy/mnt/custom_nodes/ComfyUI-Manager/cm-cli.py`. For example: `python3 /comfy/mnt/custom_nodes/ComfyUI-Manager/cm-cli.py show installed` (`COMFYUI_PATH=/ComfyUI` should be set) -## 5.5. Additional FAQ +## 5.5. Shell within the Docker image + +Depending on your `WANTED_UID` and `WANTED_GID`, when starting a `docker exec` (or getting a ba`bash` terminal from `docker compose`) it is possible that ythe shell is stared with incorrect permissions (we will see a `bash: /comfy/.bashrc: Permission denied` error). The `comfy` user is `sudo`-able: run `sudo su comfytoo` to get the proper UID/GID. + +## 5.6. Additional FAQ See [extras/FAQ.md] for additional FAQ topics, among which: - Updating ComfyUI @@ -422,7 +427,8 @@ It is also possible to rename the entire "run" directory for get a clean install # 7. Changelog -- 20250109: Integrated `SECURITY_LEVELS` within the docker arguments + added libGL into the base container. +- 20250116: Happy 2nd Birthday ComfyUI -- added multiple builds for different base Ubuntu OS and CUDA combinations + added `ffmpeg` into the base container. +- 20250109: Integrated `SECURITY_LEVELS` within the docker arguments + added `libGL` into the base container. - 20240915: Added `COMFY_CMDLINE_BASE` and `COMFY_CMDLINE_XTRA` variable - 20240824: Tag 0.2: shift to pull at first run-time, user upgradable with lighter base container - 20240824: Tag 0.1: builds were based on ComfyUI release, not user upgradable