Skip to content

Commit b222acc

Browse files
committed
Add dockerization
1 parent 9f90588 commit b222acc

5 files changed

Lines changed: 287 additions & 0 deletions

File tree

docker/Dockerfile

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
FROM alpine/git:2.36.2 as download
2+
3+
COPY clone.sh /clone.sh
4+
5+
6+
RUN . /clone.sh stable-diffusion-stability-ai https://github.com/Stability-AI/stablediffusion.git cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf \
7+
&& rm -rf assets data/**/*.png data/**/*.jpg data/**/*.gif
8+
9+
RUN . /clone.sh CodeFormer https://github.com/sczhou/CodeFormer.git c5b4593074ba6214284d6acd5f1719b6c5d739af \
10+
&& rm -rf assets inputs
11+
12+
RUN . /clone.sh BLIP https://github.com/salesforce/BLIP.git 48211a1594f1321b00f14c9f7a5b4813144b2fb9
13+
RUN . /clone.sh k-diffusion https://github.com/crowsonkb/k-diffusion.git ab527a9a6d347f364e3d185ba6d714e22d80cb3c
14+
RUN . /clone.sh clip-interrogator https://github.com/pharmapsychotic/clip-interrogator 2cf03aaf6e704197fd0dae7c7f96aa59cf1b11c9
15+
RUN . /clone.sh generative-models https://github.com/Stability-AI/generative-models 45c443b316737a4ab6e40413d7794a7f5657c19f
16+
17+
18+
FROM alpine:3.17 as xformers
19+
RUN apk add --no-cache aria2
20+
RUN aria2c -x 5 --dir / --out wheel.whl 'https://github.com/AbdBarho/stable-diffusion-webui-docker/releases/download/6.0.0/xformers-0.0.21.dev544-cp310-cp310-manylinux2014_x86_64-pytorch201.whl'
21+
22+
23+
FROM python:3.10.9-slim
24+
25+
ENV DEBIAN_FRONTEND=noninteractive PIP_PREFER_BINARY=1
26+
27+
RUN --mount=type=cache,target=/var/cache/apt \
28+
apt-get update && \
29+
# we need those
30+
apt-get install -y fonts-dejavu-core rsync git jq moreutils aria2 \
31+
# extensions needs those
32+
ffmpeg libglfw3-dev libgles2-mesa-dev pkg-config libcairo2 libcairo2-dev build-essential
33+
34+
35+
RUN --mount=type=cache,target=/cache --mount=type=cache,target=/root/.cache/pip \
36+
aria2c -x 5 --dir /cache --out torch-2.0.1-cp310-cp310-linux_x86_64.whl -c \
37+
https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp310-cp310-linux_x86_64.whl && \
38+
pip install /cache/torch-2.0.1-cp310-cp310-linux_x86_64.whl torchvision --index-url https://download.pytorch.org/whl/cu118
39+
40+
41+
RUN --mount=type=cache,target=/root/.cache/pip \
42+
git clone https://github.com/doublesymmetry/stable-diffusion-webui.git && \
43+
cd stable-diffusion-webui && \
44+
git reset --hard 9f90588c7e8f3e801d6a60196716e4335b81e65a && \
45+
pip install -r requirements_versions.txt
46+
47+
RUN --mount=type=cache,target=/root/.cache/pip \
48+
--mount=type=bind,from=xformers,source=/wheel.whl,target=/xformers-0.0.21.dev544-cp310-cp310-manylinux2014_x86_64.whl \
49+
pip install /xformers-0.0.21.dev544-cp310-cp310-manylinux2014_x86_64.whl
50+
51+
ENV ROOT=/stable-diffusion-webui
52+
53+
54+
COPY --from=download /repositories/ ${ROOT}/repositories/
55+
RUN mkdir ${ROOT}/interrogate && cp ${ROOT}/repositories/clip-interrogator/clip_interrogator/data/* ${ROOT}/interrogate
56+
RUN --mount=type=cache,target=/root/.cache/pip \
57+
pip install -r ${ROOT}/repositories/CodeFormer/requirements.txt
58+
59+
RUN --mount=type=cache,target=/root/.cache/pip \
60+
pip install pyngrok \
61+
git+https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379 \
62+
git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1 \
63+
git+https://github.com/mlfoundations/open_clip.git@bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b
64+
65+
66+
# Note: don't update the sha of previous versions because the install will take forever
67+
# instead, update the repo state in a later step
68+
69+
# TODO: either remove if fixed in A1111 (unlikely) or move to the top with other apt stuff
70+
RUN apt-get -y install libgoogle-perftools-dev && apt-get clean
71+
ENV LD_PRELOAD=libtcmalloc.so
72+
73+
ARG SHA=9f90588c7e8f3e801d6a60196716e4335b81e65a
74+
RUN --mount=type=cache,target=/root/.cache/pip \
75+
cd stable-diffusion-webui && \
76+
git fetch && \
77+
git reset --hard ${SHA} && \
78+
pip install -r requirements_versions.txt
79+
80+
# Download deliberate model into the right place
81+
# Download from: https://civitai.com/api/download/models/15236
82+
# Should go into: ${ROOT}/models/Stable-diffusion/deliberate-v2.safetensors
83+
RUN cd stable-diffusion-webui && \
84+
mkdir -p models/Stable-diffusion && \
85+
aria2c -x 5 --dir models/Stable-diffusion --out deliberate-v2.safetensors -c \
86+
https://civitai.com/api/download/models/15236
87+
88+
89+
COPY . /docker
90+
91+
RUN \
92+
python3 /docker/info.py ${ROOT}/modules/ui.py && \
93+
mv ${ROOT}/style.css ${ROOT}/user.css && \
94+
# one of the ugliest hacks I ever wrote \
95+
sed -i 's/in_app_dir = .*/in_app_dir = True/g' /usr/local/lib/python3.10/site-packages/gradio/routes.py && \
96+
git config --global --add safe.directory '*'
97+
98+
WORKDIR ${ROOT}
99+
ENV NVIDIA_VISIBLE_DEVICES=all
100+
ENV CLI_ARGS="--listen --nowebui --port=7860 --cors-allow-origins=*"
101+
EXPOSE 7860
102+
ENTRYPOINT ["/docker/entrypoint.sh"]
103+
CMD python -u webui.py --listen --port 7860 ${CLI_ARGS}

docker/clone.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
set -Eeuox pipefail
4+
5+
mkdir -p /repositories/"$1"
6+
cd /repositories/"$1"
7+
git init
8+
git remote add origin "$2"
9+
git fetch origin "$3" --depth=1
10+
git reset --hard "$3"
11+
rm -rf .git

docker/config.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
3+
"""Checks and sets default values for config.json before starting the container."""
4+
5+
import json
6+
import re
7+
import os.path
8+
import sys
9+
10+
DEFAULT_FILEPATH = '/data/config/auto/config.json'
11+
12+
DEFAULT_OUTDIRS = {
13+
"outdir_samples": "",
14+
"outdir_txt2img_samples": "/output/txt2img",
15+
"outdir_img2img_samples": "/output/img2img",
16+
"outdir_extras_samples": "/output/extras",
17+
"outdir_grids": "",
18+
"outdir_txt2img_grids": "/output/txt2img-grids",
19+
"outdir_img2img_grids": "/output/img2img-grids",
20+
"outdir_save": "/output/saved",
21+
"outdir_init_images": "/output/init-images",
22+
}
23+
RE_VALID_OUTDIR = re.compile(r"(^/output(/\.?[\w\-\_]+)+/?$)|(^\s?$)")
24+
25+
DEFAULT_OTHER = {
26+
"font": "DejaVuSans.ttf",
27+
}
28+
29+
def dict_to_json_file(target_file: str, data: dict):
30+
"""Write dictionary to specified json file"""
31+
32+
with open(target_file, 'w') as f:
33+
json.dump(data, f)
34+
35+
def json_file_to_dict(config_file: str) -> dict|None:
36+
"""Load json file into a dictionary. Return None if file does not exist."""
37+
38+
if os.path.isfile(config_file):
39+
with open(config_file, 'r') as f:
40+
return json.load(f)
41+
else:
42+
return None
43+
44+
def replace_if_invalid(value: str, replacement: str, pattern: str|re.Pattern[str]) -> str:
45+
"""Returns original value if valid, fallback value if invalid"""
46+
47+
if re.match(pattern, value):
48+
return value
49+
else:
50+
return replacement
51+
52+
def check_and_replace_config(config_file: str, target_file: str = None):
53+
"""Checks given file for invalid values. Replaces those with fallback values (default: overwrites file)."""
54+
55+
# Get current user config, or empty if file does not exists
56+
data = json_file_to_dict(config_file) or {}
57+
58+
# Check and fix output directories
59+
for k, def_val in DEFAULT_OUTDIRS.items():
60+
if k not in data:
61+
data[k] = def_val
62+
else:
63+
data[k] = replace_if_invalid(value=data[k], replacement=def_val, pattern=RE_VALID_OUTDIR)
64+
65+
# Check and fix other default settings
66+
for k, def_val in DEFAULT_OTHER.items():
67+
if k not in data:
68+
data[k] = def_val
69+
70+
# Write results to file
71+
dict_to_json_file(target_file or config_file, data)
72+
73+
if __name__ == '__main__':
74+
if len(sys.argv) > 1:
75+
check_and_replace_config(*sys.argv[1:])
76+
else:
77+
check_and_replace_config(DEFAULT_FILEPATH)
78+

docker/entrypoint.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
3+
set -Eeuo pipefail
4+
5+
# TODO: move all mkdir -p ?
6+
mkdir -p /data/config/auto/scripts/
7+
# mount scripts individually
8+
find "${ROOT}/scripts/" -maxdepth 1 -type l -delete
9+
cp -vrfTs /data/config/auto/scripts/ "${ROOT}/scripts/"
10+
11+
# Set up config file
12+
python /docker/config.py /data/config/auto/config.json
13+
14+
if [ ! -f /data/config/auto/ui-config.json ]; then
15+
echo '{}' >/data/config/auto/ui-config.json
16+
fi
17+
18+
if [ ! -f /data/config/auto/styles.csv ]; then
19+
touch /data/config/auto/styles.csv
20+
fi
21+
22+
# copy models from original models folder
23+
mkdir -p /data/models/VAE-approx/ /data/models/karlo/
24+
25+
rsync -a --info=NAME ${ROOT}/models/VAE-approx/ /data/models/VAE-approx/
26+
rsync -a --info=NAME ${ROOT}/models/karlo/ /data/models/karlo/
27+
28+
declare -A MOUNTS
29+
30+
MOUNTS["/root/.cache"]="/data/.cache"
31+
MOUNTS["${ROOT}/models"]="/data/models"
32+
33+
MOUNTS["${ROOT}/embeddings"]="/data/embeddings"
34+
MOUNTS["${ROOT}/config.json"]="/data/config/auto/config.json"
35+
MOUNTS["${ROOT}/ui-config.json"]="/data/config/auto/ui-config.json"
36+
MOUNTS["${ROOT}/styles.csv"]="/data/config/auto/styles.csv"
37+
MOUNTS["${ROOT}/extensions"]="/data/config/auto/extensions"
38+
MOUNTS["${ROOT}/config_states"]="/data/config/auto/config_states"
39+
40+
# extra hacks
41+
MOUNTS["${ROOT}/repositories/CodeFormer/weights/facelib"]="/data/.cache"
42+
43+
for to_path in "${!MOUNTS[@]}"; do
44+
set -Eeuo pipefail
45+
from_path="${MOUNTS[${to_path}]}"
46+
rm -rf "${to_path}"
47+
if [ ! -f "$from_path" ]; then
48+
mkdir -vp "$from_path"
49+
fi
50+
mkdir -vp "$(dirname "${to_path}")"
51+
ln -sT "${from_path}" "${to_path}"
52+
echo Mounted $(basename "${from_path}")
53+
done
54+
55+
echo "Installing extension dependencies (if any)"
56+
57+
# because we build our container as root:
58+
chown -R root ~/.cache/
59+
chmod 766 ~/.cache/
60+
61+
shopt -s nullglob
62+
# For install.py, please refer to https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions#installpy
63+
list=(./extensions/*/install.py)
64+
for installscript in "${list[@]}"; do
65+
EXTNAME=`echo $installscript | cut -d '/' -f 3`
66+
# Skip installing dependencies if extension is disabled in config
67+
if `jq -e ".disabled_extensions|any(. == \"$EXTNAME\")" config.json`; then
68+
echo "Skipping disabled extension ($EXTNAME)"
69+
continue
70+
fi
71+
PYTHONPATH=${ROOT} python "$installscript"
72+
done
73+
74+
if [ -f "/data/config/auto/startup.sh" ]; then
75+
pushd ${ROOT}
76+
echo "Running startup script"
77+
. /data/config/auto/startup.sh
78+
popd
79+
fi
80+
81+
exec "$@"

docker/info.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import sys
2+
from pathlib import Path
3+
4+
file = Path(sys.argv[1])
5+
file.write_text(
6+
file.read_text()\
7+
.replace(' return demo', """
8+
with demo:
9+
gr.Markdown(
10+
'Created by [AUTOMATIC1111 / stable-diffusion-webui-docker](https://github.com/AbdBarho/stable-diffusion-webui-docker/)'
11+
)
12+
return demo
13+
""", 1)
14+
)

0 commit comments

Comments
 (0)