Skip to content

Commit ec3cfec

Browse files
authored
feat: DEV-3031: Add uwsgi and nginx (#2868)
1 parent 19d78b0 commit ec3cfec

File tree

18 files changed

+483
-158
lines changed

18 files changed

+483
-158
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
# Except:
55
!deploy/docker-entrypoint.d/**
6+
!deploy/nginx/**
67
!deploy/docker-entrypoint.sh
78
!deploy/requirements-mw.txt
89
!deploy/requirements.txt
910
!deploy/heroku_run.sh
11+
!deploy/uwsgi.ini
1012
!label_studio/**
1113
!setup.py
1214
!README.md

Dockerfile

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
# syntax=docker/dockerfile:1.3
22
FROM node:14 AS frontend-builder
33

4-
ENV NPM_CACHE_LOCATION=/root/.npm \
4+
ENV NPM_CACHE_LOCATION=$HOME/.npm \
55
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
66

77
WORKDIR /label-studio/label_studio/frontend
88

9-
COPY label_studio/frontend .
10-
COPY label_studio/__init__.py /label-studio/label_studio/__init__.py
9+
COPY --chown=1001:0 label_studio/frontend .
10+
COPY --chown=1001:0 label_studio/__init__.py /label-studio/label_studio/__init__.py
1111

12-
RUN --mount=type=cache,target=$NPM_CACHE_LOCATION \
12+
RUN --mount=type=cache,target=$NPM_CACHE_LOCATION,uid=1001,gid=0 \
1313
npm ci \
1414
&& npm run build:production
1515

1616
FROM ubuntu:20.04
1717

1818
ENV DEBIAN_FRONTEND=noninteractive \
1919
LS_DIR=/label-studio \
20-
PIP_CACHE_DIR=/.cache \
20+
PIP_CACHE_DIR=$HOME/.cache \
2121
DJANGO_SETTINGS_MODULE=core.settings.label_studio \
2222
LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
23+
OPT_DIR=/opt/heartex/instance-data/etc \
2324
SETUPTOOLS_USE_DISTUTILS=stdlib
2425

2526
WORKDIR $LS_DIR
@@ -29,26 +30,46 @@ RUN set -eux \
2930
&& apt-get update \
3031
&& apt-get install --no-install-recommends --no-install-suggests -y \
3132
build-essential postgresql-client libmysqlclient-dev mysql-client python3.8 python3-pip python3.8-dev \
32-
git libxml2-dev libxslt-dev zlib1g-dev
33+
git libxml2-dev libxslt-dev zlib1g-dev gnupg curl lsb-release && \
34+
apt-get purge --assume-yes --auto-remove --option APT::AutoRemove::RecommendsImportant=false \
35+
--option APT::AutoRemove::SuggestsImportant=false && rm -rf /var/lib/apt/lists/* /tmp/*
36+
37+
RUN --mount=type=cache,target=$PIP_CACHE_DIR,uid=1001,gid=0 \
38+
pip3 install --upgrade pip setuptools && pip3 install uwsgi uwsgitop
39+
40+
# incapsulate nginx install & configure to a single layer
41+
RUN set -eux; \
42+
curl -sSL https://nginx.org/keys/nginx_signing.key | apt-key add - && \
43+
echo "deb https://nginx.org/packages/mainline/ubuntu/ $(lsb_release -cs) nginx" >> /etc/apt/sources.list && \
44+
apt-get update && apt-get install nginx && \
45+
apt-get purge --assume-yes --auto-remove --option APT::AutoRemove::RecommendsImportant=false \
46+
--option APT::AutoRemove::SuggestsImportant=false && rm -rf /var/lib/apt/lists/* /tmp/* && \
47+
nginx -v
3348

3449
# Copy and install middleware dependencies
35-
COPY deploy/requirements-mw.txt .
36-
RUN --mount=type=cache,target=$PIP_CACHE_DIR \
50+
COPY --chown=1001:0 deploy/requirements-mw.txt .
51+
RUN --mount=type=cache,target=$PIP_CACHE_DIR,uid=1001,gid=0 \
3752
pip3 install -r requirements-mw.txt
3853

3954
# Copy and install requirements.txt first for caching
40-
COPY deploy/requirements.txt .
41-
RUN --mount=type=cache,target=$PIP_CACHE_DIR \
55+
COPY --chown=1001:0 deploy/requirements.txt .
56+
RUN --mount=type=cache,target=$PIP_CACHE_DIR,uid=1001,gid=0 \
4257
pip3 install -r requirements.txt
4358

44-
COPY . .
45-
RUN --mount=type=cache,target=$PIP_CACHE_DIR \
46-
pip3 install -e .
59+
COPY --chown=1001:0 . .
60+
RUN --mount=type=cache,target=$PIP_CACHE_DIR,uid=1001,gid=0 \
61+
pip3 install -e . && \
62+
chown -R 1001:0 $LS_DIR && \
63+
chmod -R g=u $LS_DIR
4764

4865
RUN rm -rf ./label_studio/frontend
49-
COPY --from=frontend-builder /label-studio/label_studio/frontend/dist ./label_studio/frontend/dist
66+
COPY --chown=1001:0 --from=frontend-builder /label-studio/label_studio/frontend/dist ./label_studio/frontend/dist
67+
68+
RUN python3 label_studio/manage.py collectstatic --no-input && \
69+
chown -R 1001:0 $LS_DIR && \
70+
chmod -R g=u $LS_DIR
5071

51-
RUN python3 label_studio/manage.py collectstatic --no-input
72+
ENV HOME=/label-studio
5273

5374
EXPOSE 8080
5475

Dockerfile.redhat

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,23 @@ FROM registry.access.redhat.com/ubi8/python-39
1818
ENV LS_DIR=/label-studio \
1919
PIP_CACHE_DIR=$HOME/.cache \
2020
DJANGO_SETTINGS_MODULE=core.settings.label_studio \
21-
LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data
21+
LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
22+
OPT_DIR=/opt/heartex/instance-data/etc \
23+
SETUPTOOLS_USE_DISTUTILS=stdlib
24+
25+
USER 0
26+
RUN dnf -y install nginx && \
27+
mkdir -p $OPT_DIR && \
28+
chown -R 1001:0 $OPT_DIR /etc/nginx/nginx.conf && \
29+
chmod -R g=u $OPT_DIR /etc/nginx/nginx.conf
30+
USER 1001
2231

2332
WORKDIR $LS_DIR
2433

2534
# Copy and install middleware dependencies
2635
COPY --chown=1001:0 deploy/requirements-mw.txt .
2736
RUN --mount=type=cache,target=$PIP_CACHE_DIR,uid=1001,gid=0 \
37+
pip3 install uwsgi uwsgitop && \
2838
pip3 install -r requirements-mw.txt
2939

3040
# Copy and install requirements.txt first for caching
@@ -53,6 +63,7 @@ LABEL name="LabelStudio" \
5363

5464
COPY --chown=1001:0 licenses/ /licenses
5565
RUN cp $LS_DIR/LICENSE /licenses
66+
ENV HOME=/label-studio
5667

5768
ENTRYPOINT ["./deploy/docker-entrypoint.sh"]
5869
CMD ["label-studio"]

deploy/docker-entrypoint.d/10-copy-static-data.sh

Lines changed: 0 additions & 16 deletions
This file was deleted.

deploy/docker-entrypoint.d/20-wait-for-db.sh

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,72 @@
1-
#!/bin/sh
1+
#!/bin/bash
2+
23
set -e ${DEBUG:+-x}
34

4-
if [ -n "${POSTGRE_HOST:-}" ]; then
5+
function copy_and_export() {
6+
dest_dir=$OPT_DIR/_pg_ssl_certs
7+
mkdir -p $dest_dir
8+
local src_path=$1
9+
if [[ -f "$src_path" ]]; then
10+
src_filename=$(basename -- $src_path)
11+
cp $src_path $dest_dir/
12+
chmod 600 $dest_dir/$src_filename
13+
echo "$dest_dir/$src_filename"
14+
fi
15+
}
16+
17+
function save_and_export() {
18+
local key=$1
19+
local value=$2
20+
export "$key"="$value"
21+
echo "export $1=$2" >>"$OPT_DIR"/config_env
22+
}
23+
24+
function postgres_ssl_setup() {
25+
# workaround to deal with immutable k8s secrets
26+
if [[ ${POSTGRE_SSL_MODE:-} == 'verify-ca' || ${POSTGRE_SSL_MODE:-} == 'verify-full' ]]; then
27+
if [[ -z ${POSTGRE_SSLROOTCERT:-} ]]; then
28+
echo >&3 "=>POSTGRE_SSLROOTCERT is required"
29+
exit 1
30+
else
31+
save_and_export PGSSLMODE "$POSTGRE_SSL_MODE"
32+
save_and_export PGSSLROOTCERT "$(copy_and_export $POSTGRE_SSLROOTCERT)"
33+
fi
34+
if [[ ${POSTGRE_SSL_MODE:-} == 'verify-full' ]]; then
35+
if [[ -z ${POSTGRE_SSLCERT:-} || -z ${POSTGRE_SSLKEY:-} ]]; then
36+
echo >&3 "=> One of required variables POSTGRE_SSLCERT or POSTGRE_SSLKEY were not set"
37+
exit 1
38+
fi
39+
fi
40+
if [[ -n ${POSTGRE_SSLCERT:-} ]]; then
41+
save_and_export PGSSLCERT "$(copy_and_export $POSTGRE_SSLCERT)"
42+
fi
43+
if [[ -n ${POSTGRE_SSLKEY:-} ]]; then
44+
save_and_export PGSSLKEY "$(copy_and_export $POSTGRE_SSLKEY)"
45+
fi
46+
elif [[ ${POSTGRE_SSL_MODE:-} == 'disable' || ${POSTGRE_SSL_MODE:-} == 'allow' || ${POSTGRE_SSL_MODE:-} == 'prefer' || ${POSTGRE_SSL_MODE:-} == 'require' ]]; then
47+
save_and_export PGSSLMODE "$POSTGRE_SSL_MODE"
48+
fi
49+
}
50+
51+
function postgres_ready(){
52+
python3 << END
53+
import sys
54+
import os
55+
import psycopg2
56+
try:
57+
conn = psycopg2.connect(dbname=os.getenv('POSTGRE_NAME', 'root'), user=os.getenv('POSTGRE_USER'), password=os.getenv('POSTGRE_PASSWORD'), host=os.getenv('POSTGRE_HOST'), port=os.getenv('POSTGRE_PORT'), sslmode=os.getenv('PGSSLMODE'), sslrootcert=os.getenv('PGSSLROOTCERT'), sslcert=os.getenv('PGSSLCERT'), sslkey=os.getenv('PGSSLKEY'))
58+
except psycopg2.OperationalError as e:
59+
print(e)
60+
sys.exit(-1)
61+
sys.exit(0)
62+
END
63+
}
64+
65+
66+
if [[ -n "${POSTGRE_HOST:-}" ]]; then
67+
postgres_ssl_setup
568
echo >&3 "=> Waiting for postgres..."
6-
until PGPASSWORD=$POSTGRE_PASSWORD psql -h "$POSTGRE_HOST" -p $POSTGRE_PORT -U "$POSTGRE_USER" -d "${POSTGRE_NAME:=root}" -c '\q'; do
69+
until postgres_ready; do
770
echo >&3 "==> Postgres is unavailable - sleeping..."
871
sleep 1
972
done

deploy/docker-entrypoint.d/30-run-db-migrations.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/sh
22
set -e ${DEBUG:+-x}
33

4-
if [ -n "${POSTGRE_HOST:-}" ] || [ -n "${MYSQL_HOST:-}" ]; then
4+
if [ -n "${POSTGRE_HOST:-}" ] || [ -n "${MYSQL_HOST:-}" ] && [ "${SKIP_DB_MIGRATIONS:-}" != "true" ]; then
55
echo >&3 "=> Do database migrations..."
66
python3 /label-studio/label_studio/manage.py migrate >&3
77
echo >&3 "=> Migrations completed."

deploy/docker-entrypoint.sh

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,55 @@
11
#!/bin/sh
22

3-
set -e
3+
set -e ${DEBUG:+-x}
44

55
# Redirect all scripts output + leaving stdout to container payload.
66
exec 3>&1
77

8-
if [ "$1" = "label-studio" ]; then
9-
if /usr/bin/find "/label-studio/deploy/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
10-
echo >&3 "$0: Looking for init scripts in /label-studio/deploy/docker-entrypoint.d/"
11-
find "/label-studio/deploy/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do
12-
case "$f" in
13-
*.sh)
14-
if [ -x "$f" ]; then
15-
echo >&3 "$0: Launching $f";
16-
"$f"
17-
else
18-
# warn on shell scripts without exec bit
19-
echo >&3 "$0: Ignoring $f, not executable";
20-
fi
21-
;;
22-
*) echo >&3 "$0: Ignoring $f";;
23-
esac
24-
done
8+
ENTRYPOINT_PATH=/label-studio/deploy
259

26-
echo >&3 "$0: Configuration complete; ready for start up"
27-
else
28-
echo >&3 "$0: No init scripts found in /label-studio/deploy/docker-entrypoint.d/, skipping configuration"
10+
uid_entrypoint() {
11+
if ! whoami 2>/dev/null; then
12+
if [ -w /etc/passwd ]; then
13+
echo "labelstudio::$(id -u):0:labelstudio user:${HOME}:/bin/bash" >>/etc/passwd
2914
fi
30-
fi
15+
fi
16+
}
3117

32-
exec "$@"
18+
exec_entrypoint() {
19+
if /usr/bin/find -L "$1" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
20+
echo >&3 "$0: Looking for init scripts in $1"
21+
find "$1" -follow -type f -print | sort -V | while read -r f; do
22+
case "$f" in
23+
*.sh)
24+
if [ -x "$f" ]; then
25+
echo >&3 "$0: Launching $f"
26+
"$f"
27+
else
28+
# warn on shell scripts without exec bit
29+
echo >&3 "$0: Ignoring $f, not executable"
30+
fi
31+
;;
32+
*) echo >&3 "$0: Ignoring $f" ;;
33+
esac
34+
done
35+
if [ -f $OPT_DIR/config_env ]; then
36+
. $OPT_DIR/config_env
37+
fi
38+
echo >&3 "$0: Configuration complete; ready for start up"
39+
else
40+
echo >&3 "$0: No init scripts found in $1, skipping configuration"
41+
fi
42+
}
43+
44+
uid_entrypoint
45+
46+
if [ "$1" = "nginx" ]; then
47+
exec_entrypoint "$ENTRYPOINT_PATH/nginx/scripts/"
48+
exec nginx -c /etc/nginx/nginx.conf
49+
elif [ "$1" = "label-studio-uwsgi" ]; then
50+
exec_entrypoint "$ENTRYPOINT_PATH/docker-entrypoint.d/"
51+
exec uwsgi --ini /label-studio/deploy/uwsgi.ini
52+
else
53+
exec_entrypoint "$ENTRYPOINT_PATH/docker-entrypoint.d/"
54+
exec "$@"
55+
fi

0 commit comments

Comments
 (0)