diff --git a/.dockerignore b/.dockerignore index 429ca125c0..81301571b8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,21 +3,23 @@ !invokeai !ldm !pyproject.toml -!README.md # Guard against pulling in any models that might exist in the directory tree **/*.pt* **/*.ckpt # ignore frontend but whitelist dist -invokeai/frontend/** -!invokeai/frontend/dist +invokeai/frontend/ +!invokeai/frontend/dist/ # ignore invokeai/assets but whitelist invokeai/assets/web -invokeai/assets -!invokeai/assets/web +invokeai/assets/ +!invokeai/assets/web/ -# ignore python cache -**/__pycache__ +# Byte-compiled / optimized / DLL files +**/__pycache__/ **/*.py[cod] -**/*.egg-info + +# Distribution / packaging +*.egg-info/ +*.egg diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index b340768dd2..b5daf76932 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -3,7 +3,8 @@ on: push: branches: - 'main' - - 'update/ci/*' + - 'update/ci/docker/*' + - 'update/docker/*' tags: - 'v*.*.*' @@ -20,18 +21,15 @@ jobs: include: - flavor: amd pip-extra-index-url: 'https://download.pytorch.org/whl/rocm5.2' - dockerfile: docker/Dockerfile - platforms: linux/amd64,linux/arm64 - flavor: cuda pip-extra-index-url: '' - dockerfile: docker/Dockerfile - platforms: linux/amd64,linux/arm64 - flavor: cpu pip-extra-index-url: 'https://download.pytorch.org/whl/cpu' - dockerfile: docker/Dockerfile - platforms: linux/amd64,linux/arm64 runs-on: ubuntu-latest name: ${{ matrix.flavor }} + env: + PLATFORMS: 'linux/amd64,linux/arm64' + DOCKERFILE: 'docker/Dockerfile' steps: - name: Checkout uses: actions/checkout@v3 @@ -41,7 +39,9 @@ jobs: uses: docker/metadata-action@v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} - images: ghcr.io/${{ github.repository }} + images: | + ghcr.io/${{ github.repository }} + ${{ vars.DOCKERHUB_REPOSITORY }} tags: | type=ref,event=branch type=ref,event=tag @@ -52,13 +52,14 @@ jobs: flavor: | latest=${{ matrix.flavor == 'cuda' && github.ref == 'refs/heads/main' }} suffix=-${{ matrix.flavor }},onlatest=false + - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: - platforms: ${{ matrix.platforms }} + platforms: ${{ env.PLATFORMS }} - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' @@ -68,25 +69,34 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to Docker Hub + if: github.event_name != 'pull_request' && vars.DOCKERHUB_REPOSITORY != '' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build container + id: docker_build uses: docker/build-push-action@v4 with: context: . - file: ${{ matrix.dockerfile }} - platforms: ${{ matrix.platforms }} - push: ${{ github.event_name != 'pull_request' }} + file: ${{ env.DOCKERFILE }} + platforms: ${{ env.PLATFORMS }} + push: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/tags/*' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: PIP_EXTRA_INDEX_URL=${{ matrix.pip-extra-index-url }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: | + type=gha,scope=${{ github.ref_name }}-${{ matrix.flavor }} + type=gha,scope=main-${{ matrix.flavor }} + cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.flavor }} - - name: Output image, digest and metadata to summary - run: | - { - echo imageid: "${{ steps.docker_build.outputs.imageid }}" - echo digest: "${{ steps.docker_build.outputs.digest }}" - echo labels: "${{ steps.meta.outputs.labels }}" - echo tags: "${{ steps.meta.outputs.tags }}" - echo version: "${{ steps.meta.outputs.version }}" - } >> "$GITHUB_STEP_SUMMARY" + - name: Docker Hub Description + if: github.ref == 'refs/heads/main' || github.ref == 'refs/tags/*' && vars.DOCKERHUB_REPOSITORY != '' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: ${{ vars.DOCKERHUB_REPOSITORY }} + short-description: ${{ github.event.repository.description }} diff --git a/docker/Dockerfile b/docker/Dockerfile index d957e72334..a3619d15ab 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,57 +1,63 @@ # syntax=docker/dockerfile:1 + ARG PYTHON_VERSION=3.9 ################## ## base image ## ################## FROM python:${PYTHON_VERSION}-slim AS python-base +LABEL org.opencontainers.image.authors="mauwii@outlook.de" + # prepare for buildkit cache -RUN rm -f /etc/apt/apt.conf.d/docker-clean +RUN rm -f /etc/apt/apt.conf.d/docker-clean \ + && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache # Install necesarry packages RUN \ --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update \ - && apt-get install \ - -yqq \ + && apt-get install -y \ --no-install-recommends \ libgl1-mesa-glx=20.3.* \ libglib2.0-0=2.66.* \ - libopencv-dev=4.5.* \ - && rm -rf /var/lib/apt/lists/* + libopencv-dev=4.5.* -# set working directory and path +# set working directory and env ARG APPDIR=/usr/src ARG APPNAME=InvokeAI WORKDIR ${APPDIR} -ENV PATH=${APPDIR}/${APPNAME}/bin:$PATH +ENV PATH ${APPDIR}/${APPNAME}/bin:$PATH +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE 1 +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED 1 +# don't fall back to legacy build system +ENV PIP_USE_PEP517=1 ####################### ## build pyproject ## ####################### FROM python-base AS pyproject-builder -ENV PIP_USE_PEP517=1 -# prepare for buildkit cache +# Install dependencies +RUN \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y \ + --no-install-recommends \ + build-essential=12.9 \ + gcc=4:10.2.* \ + python3-dev=3.9.* + +# prepare pip for buildkit cache ARG PIP_CACHE_DIR=/var/cache/buildkit/pip ENV PIP_CACHE_DIR ${PIP_CACHE_DIR} RUN mkdir -p ${PIP_CACHE_DIR} -# Install dependencies -RUN \ - --mount=type=cache,target=${PIP_CACHE_DIR} \ - --mount=type=cache,target=/var/cache/apt,sharing=locked \ - apt-get update \ - && apt-get install \ - -yqq \ - --no-install-recommends \ - build-essential=12.9 \ - gcc=4:10.2.* \ - python3-dev=3.9.* \ - && rm -rf /var/lib/apt/lists/* - # create virtual environment -RUN --mount=type=cache,target=${PIP_CACHE_DIR} \ +RUN --mount=type=cache,target=${PIP_CACHE_DIR},sharing=locked \ python3 -m venv "${APPNAME}" \ --upgrade-deps @@ -61,9 +67,8 @@ COPY --link . . # install pyproject.toml ARG PIP_EXTRA_INDEX_URL ENV PIP_EXTRA_INDEX_URL ${PIP_EXTRA_INDEX_URL} -ARG PIP_PACKAGE=. -RUN --mount=type=cache,target=${PIP_CACHE_DIR} \ - "${APPDIR}/${APPNAME}/bin/pip" install ${PIP_PACKAGE} +RUN --mount=type=cache,target=${PIP_CACHE_DIR},sharing=locked \ + "${APPNAME}/bin/pip" install . # build patchmatch RUN python3 -c "from patchmatch import patch_match" @@ -73,14 +78,26 @@ RUN python3 -c "from patchmatch import patch_match" ##################### FROM python-base AS runtime -# setup environment -COPY --from=pyproject-builder --link ${APPDIR}/${APPNAME} ${APPDIR}/${APPNAME} -ENV INVOKEAI_ROOT=/data -ENV INVOKE_MODEL_RECONFIGURE="--yes --default_only" +# Create a new User +ARG UNAME=appuser +RUN useradd \ + --no-log-init \ + -m \ + -U \ + "${UNAME}" -# set Entrypoint and default CMD +# create volume directory +ARG VOLUME_DIR=/data +RUN mkdir -p "${VOLUME_DIR}" \ + && chown -R "${UNAME}" "${VOLUME_DIR}" + +# setup runtime environment +USER ${UNAME} +COPY --chown=${UNAME} --from=pyproject-builder ${APPDIR}/${APPNAME} ${APPNAME} +ENV INVOKEAI_ROOT ${VOLUME_DIR} +ENV TRANSFORMERS_CACHE ${VOLUME_DIR}/.cache +ENV INVOKE_MODEL_RECONFIGURE "--yes --default_only" +EXPOSE 9090 ENTRYPOINT [ "invokeai" ] -CMD [ "--web", "--host=0.0.0.0" ] -VOLUME [ "/data" ] - -LABEL org.opencontainers.image.authors="mauwii@outlook.de" +CMD [ "--web", "--host", "0.0.0.0", "--port", "9090" ] +VOLUME [ "${VOLUME_DIR}" ] diff --git a/docker/build.sh b/docker/build.sh index dc1a1dcc78..7dc98ec208 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -1,19 +1,24 @@ #!/usr/bin/env bash set -e -# How to use: https://invoke-ai.github.io/InvokeAI/installation/INSTALL_DOCKER/#setup -# Some possible pip extra-index urls (cuda 11.7 is available without extra url): -# CUDA 11.6: https://download.pytorch.org/whl/cu116 -# ROCm 5.2: https://download.pytorch.org/whl/rocm5.2 -# CPU: https://download.pytorch.org/whl/cpu -# as found on https://pytorch.org/get-started/locally/ +# If you want to build a specific flavor, set the CONTAINER_FLAVOR environment variable +# e.g. CONTAINER_FLAVOR=cpu ./build.sh +# Possible Values are: +# - cpu +# - cuda +# - rocm +# Don't forget to also set it when executing run.sh +# if it is not set, the script will try to detect the flavor by itself. +# +# Doc can be found here: +# https://invoke-ai.github.io/InvokeAI/installation/040_INSTALL_DOCKER/ -SCRIPTDIR=$(dirname "$0") +SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") cd "$SCRIPTDIR" || exit 1 source ./env.sh -DOCKERFILE=${INVOKE_DOCKERFILE:-Dockerfile} +DOCKERFILE=${INVOKE_DOCKERFILE:-./Dockerfile} # print the settings echo -e "You are using these values:\n" @@ -21,9 +26,10 @@ echo -e "Dockerfile:\t\t${DOCKERFILE}" echo -e "index-url:\t\t${PIP_EXTRA_INDEX_URL:-none}" echo -e "Volumename:\t\t${VOLUMENAME}" echo -e "Platform:\t\t${PLATFORM}" -echo -e "Registry:\t\t${CONTAINER_REGISTRY}" -echo -e "Repository:\t\t${CONTAINER_REPOSITORY}" +echo -e "Container Registry:\t${CONTAINER_REGISTRY}" +echo -e "Container Repository:\t${CONTAINER_REPOSITORY}" echo -e "Container Tag:\t\t${CONTAINER_TAG}" +echo -e "Container Flavor:\t${CONTAINER_FLAVOR}" echo -e "Container Image:\t${CONTAINER_IMAGE}\n" # Create docker volume @@ -36,8 +42,9 @@ fi # Build Container DOCKER_BUILDKIT=1 docker build \ - --platform="${PLATFORM}" \ - --tag="${CONTAINER_IMAGE}" \ + --platform="${PLATFORM:-linux/amd64}" \ + --tag="${CONTAINER_IMAGE:-invokeai}" \ + ${CONTAINER_FLAVOR:+--build-arg="CONTAINER_FLAVOR=${CONTAINER_FLAVOR}"} \ ${PIP_EXTRA_INDEX_URL:+--build-arg="PIP_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}"} \ ${PIP_PACKAGE:+--build-arg="PIP_PACKAGE=${PIP_PACKAGE}"} \ --file="${DOCKERFILE}" \ diff --git a/docker/env.sh b/docker/env.sh index d6b0699ce5..6eecb24e8e 100644 --- a/docker/env.sh +++ b/docker/env.sh @@ -1,19 +1,31 @@ #!/usr/bin/env bash +# This file is used to set environment variables for the build.sh and run.sh scripts. + +# Try to detect the container flavor if no PIP_EXTRA_INDEX_URL got specified if [[ -z "$PIP_EXTRA_INDEX_URL" ]]; then + + # Activate virtual environment if not already activated and exists + if [[ -z $VIRTUAL_ENV ]]; then + [[ -e "$(dirname "${BASH_SOURCE[0]}")/../.venv/bin/activate" ]] \ + && source "$(dirname "${BASH_SOURCE[0]}")/../.venv/bin/activate" \ + && echo "Activated virtual environment: $VIRTUAL_ENV" + fi + # Decide which container flavor to build if not specified if [[ -z "$CONTAINER_FLAVOR" ]] && python -c "import torch" &>/dev/null; then # Check for CUDA and ROCm CUDA_AVAILABLE=$(python -c "import torch;print(torch.cuda.is_available())") ROCM_AVAILABLE=$(python -c "import torch;print(torch.version.hip is not None)") - if [[ "$(uname -s)" != "Darwin" && "${CUDA_AVAILABLE}" == "True" ]]; then + if [[ "${CUDA_AVAILABLE}" == "True" ]]; then CONTAINER_FLAVOR="cuda" - elif [[ "$(uname -s)" != "Darwin" && "${ROCM_AVAILABLE}" == "True" ]]; then + elif [[ "${ROCM_AVAILABLE}" == "True" ]]; then CONTAINER_FLAVOR="rocm" else CONTAINER_FLAVOR="cpu" fi fi + # Set PIP_EXTRA_INDEX_URL based on container flavor if [[ "$CONTAINER_FLAVOR" == "rocm" ]]; then PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/rocm" @@ -26,9 +38,10 @@ fi # Variables shared by build.sh and run.sh REPOSITORY_NAME="${REPOSITORY_NAME-$(basename "$(git rev-parse --show-toplevel)")}" -VOLUMENAME="${VOLUMENAME-"${REPOSITORY_NAME,,}_data"}" +REPOSITORY_NAME="${REPOSITORY_NAME,,}" +VOLUMENAME="${VOLUMENAME-"${REPOSITORY_NAME}_data"}" ARCH="${ARCH-$(uname -m)}" -PLATFORM="${PLATFORM-Linux/${ARCH}}" +PLATFORM="${PLATFORM-linux/${ARCH}}" INVOKEAI_BRANCH="${INVOKEAI_BRANCH-$(git branch --show)}" CONTAINER_REGISTRY="${CONTAINER_REGISTRY-"ghcr.io"}" CONTAINER_REPOSITORY="${CONTAINER_REPOSITORY-"$(whoami)/${REPOSITORY_NAME}"}" diff --git a/docker/run.sh b/docker/run.sh index 5593faaa3e..c52ccb4c4d 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash set -e -# How to use: https://invoke-ai.github.io/InvokeAI/installation/INSTALL_DOCKER/#run-the-container -# IMPORTANT: You need to have a token on huggingface.co to be able to download the checkpoints!!! +# How to use: https://invoke-ai.github.io/InvokeAI/installation/040_INSTALL_DOCKER/ -SCRIPTDIR=$(dirname "$0") +SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") cd "$SCRIPTDIR" || exit 1 source ./env.sh +# Create outputs directory if it does not exist +[[ -d ./outputs ]] || mkdir ./outputs + echo -e "You are using these values:\n" echo -e "Volumename:\t${VOLUMENAME}" echo -e "Invokeai_tag:\t${CONTAINER_IMAGE}" @@ -22,10 +24,18 @@ docker run \ --name="${REPOSITORY_NAME,,}" \ --hostname="${REPOSITORY_NAME,,}" \ --mount=source="${VOLUMENAME}",target=/data \ - ${MODELSPATH:+-u "$(id -u):$(id -g)"} \ + --mount type=bind,source="$(pwd)"/outputs,target=/data/outputs \ ${MODELSPATH:+--mount="type=bind,source=${MODELSPATH},target=/data/models"} \ ${HUGGING_FACE_HUB_TOKEN:+--env="HUGGING_FACE_HUB_TOKEN=${HUGGING_FACE_HUB_TOKEN}"} \ --publish=9090:9090 \ --cap-add=sys_nice \ ${GPU_FLAGS:+--gpus="${GPU_FLAGS}"} \ - "${CONTAINER_IMAGE}" ${1:+$@} + "${CONTAINER_IMAGE}" ${@:+$@} + +# Remove Trash folder +for f in outputs/.Trash*; do + if [ -e "$f" ]; then + rm -Rf "$f" + break + fi +done