From 9ddc6ed9cbbde0a98ad7c9738b51c17bcbec1948 Mon Sep 17 00:00:00 2001 From: "Kyle D. McCormick" Date: Tue, 18 Jul 2023 10:10:21 -0400 Subject: [PATCH] build: take advantage of upstream static asset build improvements TODOs: * See if we can reduce the huge mount blocks a bit. * Merge edx-platform asset folder changes instead of patching in a PR. * Add changelog entry. * Test more thoroughly. * Circulate a TEP or some other form of proposal. * Deprecate patches that no longer exist or have changed. * Announce that buildkit is required (or pull that into a separate PR). Part of: https://github.com/openedx/wg-developer-experience/issues/166 --- tutor/commands/images.py | 4 +- tutor/templates/build/openedx/Dockerfile | 443 +++++++++++++----- .../build/openedx/bin/link-static-to-devcache | 12 + .../jobs/init/mounted-edx-platform.sh | 4 +- 4 files changed, 348 insertions(+), 115 deletions(-) create mode 100755 tutor/templates/build/openedx/bin/link-static-to-devcache diff --git a/tutor/commands/images.py b/tutor/commands/images.py index 0108fed4050..9da7a570b84 100644 --- a/tutor/commands/images.py +++ b/tutor/commands/images.py @@ -34,7 +34,9 @@ def _add_core_images_to_build( image, os.path.join("build", image), tutor_config.get_typed(config, tag, str), - (), + ( + "--target=production", + ), ) ) diff --git a/tutor/templates/build/openedx/Dockerfile b/tutor/templates/build/openedx/Dockerfile index 198dda6943d..fe833472129 100644 --- a/tutor/templates/build/openedx/Dockerfile +++ b/tutor/templates/build/openedx/Dockerfile @@ -1,30 +1,46 @@ -{% if is_buildkit_enabled() %}# syntax=docker/dockerfile:1.4{% endif %} -###### Minimal image with base system requirements for most stages +# syntax=docker/dockerfile:1.4 + + +########################################################################################################################### +############## MINIMAL +############## Minimal image with base system requirements for most stages +########################################################################################################################### FROM docker.io/ubuntu:20.04 as minimal LABEL maintainer="Overhang.io " ENV DEBIAN_FRONTEND=noninteractive -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked{% endif %} \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt update && \ apt install -y build-essential curl git language-pack-en ENV LC_ALL en_US.UTF-8 {{ patch("openedx-dockerfile-minimal") }} -###### Install python with pyenv in /opt/pyenv and create virtualenv in /openedx/venv + +########################################################################################################################### +############## PYTHON +############## Install python with pyenv in /opt/pyenv and create virtualenv in /openedx/venv +########################################################################################################################### FROM minimal as python + # https://github.com/pyenv/pyenv/wiki/Common-build-problems#prerequisites -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked {% endif %}apt update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt update && \ apt install -y libssl-dev zlib1g-dev libbz2-dev \ libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ - xz-utils tk-dev libffi-dev liblzma-dev python-openssl git + xz-utils tk-dev libffi-dev liblzma-dev python-openssl git \ + software-properties-common libmysqlclient-dev libxmlsec1-dev libgeos-dev + +ARG PYTHON_VERSION=3.8.15 +ENV PYENV_ROOT /opt/pyenv +ENV PATH /openedx/venv/bin:${PATH} +ENV VIRTUAL_ENV /openedx/venv/ +ENV XDG_CACHE_HOME /openedx/.cache # Install pyenv # https://www.python.org/downloads/ # https://github.com/pyenv/pyenv/releases -ARG PYTHON_VERSION=3.8.15 -ENV PYENV_ROOT /opt/pyenv RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v2.3.17 --depth 1 # Install Python @@ -33,10 +49,24 @@ RUN $PYENV_ROOT/bin/pyenv install $PYTHON_VERSION # Create virtualenv RUN $PYENV_ROOT/versions/$PYTHON_VERSION/bin/python -m venv /openedx/venv -###### Checkout edx-platform code +# Install the right version of pip/setuptools +RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + pip install \ + # https://pypi.org/project/setuptools/ + # https://pypi.org/project/pip/ + # https://pypi.org/project/wheel/ + setuptools==67.6.1 pip==23.0.1. wheel==0.40.0 + + +########################################################################################################################### +############## CODE +########################################################################################################################### FROM minimal as code + +###### Checkout edx-platform code ARG EDX_PLATFORM_REPOSITORY={{ EDX_PLATFORM_REPOSITORY }} ARG EDX_PLATFORM_VERSION={{ EDX_PLATFORM_VERSION }} +RUN echo break cache 200 RUN mkdir -p /openedx/edx-platform && \ git clone $EDX_PLATFORM_REPOSITORY --branch $EDX_PLATFORM_VERSION --depth 1 /openedx/edx-platform WORKDIR /openedx/edx-platform @@ -55,12 +85,24 @@ RUN git config --global user.email "tutor@overhang.io" \ {# Example: RUN curl -fsSL https://github.com/openedx/edx-platform/commit/.patch | git am #} {{ patch("openedx-dockerfile-post-git-checkout") }} -##### Empty layer with just the repo at the root. -# This is useful when overriding the build context with a host repo: -# docker build --build-context edx-platform=/path/to/edx-platform +# TODO: Temporary +# Apply these edx-platform changes which have not yet merged. +RUN curl -fsSL https://github.com/openedx/edx-platform/pull/32835.patch | git am + + +########################################################################################################################### +############## EDX-PLATFORM +############## Empty layer with just the repo at the root. +############## This is useful when overriding the build context with a host repo: +############## docker build --build-context edx-platform=/path/to/edx-platform +########################################################################################################################### FROM scratch as edx-platform COPY --from=code /openedx/edx-platform / + +########################################################################################################################### +############## LOCALES +########################################################################################################################### ###### Download extra locales to /openedx/locale/contrib/locale FROM minimal as locales ARG OPENEDX_I18N_VERSION={{ OPENEDX_COMMON_VERSION }} @@ -71,29 +113,188 @@ RUN cd /tmp \ && mv openedx-i18n-*/edx-platform/locale /openedx/locale/contrib \ && rm -rf openedx-i18n* -###### Install python requirements in virtualenv -FROM python as python-requirements -ENV PATH /openedx/venv/bin:${PATH} -ENV VIRTUAL_ENV /openedx/venv/ -ENV XDG_CACHE_HOME /openedx/.cache -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked {% endif %}apt update \ - && apt install -y software-properties-common libmysqlclient-dev libxmlsec1-dev libgeos-dev -# Install the right version of pip/setuptools -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install \ - # https://pypi.org/project/setuptools/ - # https://pypi.org/project/pip/ - # https://pypi.org/project/wheel/ - setuptools==67.6.1 pip==23.0.1. wheel==0.40.0 +########################################################################################################################### +############## FRONTEND REQUIREMENTS +########################################################################################################################### +FROM python as requirements-frontend + +WORKDIR /openedx/edx-platform + +# Install python reqs for building assets +RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/assets.txt,target=requirements/edx/assets.txt \ + pip install -r requirements/edx/assets.txt + +# Set up node env +ENV PATH /openedx/nodeenv/bin:/openedx/venv/bin:${PATH} +RUN nodeenv /openedx/nodeenv --node=16.14.0 --prebuilt + +# Install nodejs requirements +ARG NPM_REGISTRY={{ NPM_REGISTRY }} +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/package-lock.json,target=package-lock.json \ + --mount=type=bind,from=edx-platform,source=/scripts/copy-node-modules.sh,target=scripts/copy-node-modules.sh \ + --mount=type=cache,target=/root/.npm,sharing=shared \ + npm clean-install --no-audit --registry=$NPM_REGISTRY + +# Set up static root. +# By default, CMS static root is assumed to be $STUDIO_ROOT_LMS/studio. +ENV STATIC_ROOT_LMS=/openedx/staticfiles +RUN mkdir -p "$STATIC_ROOT_LMS" + + +########################################################################################################################### +############## BUNDLES: PRODUCTION +########################################################################################################################### +FROM requirements-frontend as bundles-production + +# Run webpack +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/package-lock.json,target=/openedx/edx-platform/package-lock.json \ + --mount=type=bind,from=edx-platform,source=/.babelrc,target=.babelrc \ + --mount=type=bind,from=edx-platform,source=/webpack.common.config.js,target=webpack.common.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack-config/file-lists.js,target=webpack-config/file-lists.js \ + --mount=type=bind,from=edx-platform,source=/webpack.dev.config.js,target=webpack.dev.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack.prod.config.js,target=webpack.prod.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack.builtinblocks.config.js,target=webpack.builtinblocks.config.js \ + --mount=type=bind,from=edx-platform,source=/cms/djangoapps/pipeline_js/js/xmodule.js,target=cms/djangoapps/pipeline_js/js/xmodule.js \ + --mount=type=bind,from=edx-platform,source=/cms/static,target=cms/static \ + --mount=type=bind,from=edx-platform,source=/cms/templates/,target=cms/templates/ \ + --mount=type=bind,from=edx-platform,source=/common/static/js/,target=common/static/js \ + --mount=type=bind,from=edx-platform,source=/common/static/common,target=common/static/common \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/discussion/static,target=lms/djangoapps/discussion/static \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/instructor/static,target=lms/djangoapps/instructor/static \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/support/static/support,target=lms/djangoapps/support/static/support \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/teams/static,target=lms/djangoapps/teams/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/,target=lms/static/ \ + --mount=type=bind,from=edx-platform,source=/lms/templates/,target=lms/templates/ \ + --mount=type=bind,from=edx-platform,source=/openedx/features/announcements/static,target=openedx/features/announcements/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_bookmarks/static,target=openedx/features/course_bookmarks/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_experience/static,target=openedx/features/course_experience/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_search/static,target=openedx/features/course_search/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/learner_profile/static,target=openedx/features/learner_profile/static \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + --mount=type=bind,from=edx-platform,source=/xmodule/js,target=xmodule/js \ + npm run webpack + + +########################################################################################################################### +############## BUNDLES: DEVELOPMENT +########################################################################################################################### +FROM requirements-frontend as bundles-development + +# Run webpack +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/package-lock.json,target=/openedx/edx-platform/package-lock.json \ + --mount=type=bind,from=edx-platform,source=/.babelrc,target=.babelrc \ + --mount=type=bind,from=edx-platform,source=/webpack.common.config.js,target=webpack.common.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack-config/file-lists.js,target=webpack-config/file-lists.js \ + --mount=type=bind,from=edx-platform,source=/webpack.dev.config.js,target=webpack.dev.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack.prod.config.js,target=webpack.prod.config.js \ + --mount=type=bind,from=edx-platform,source=/webpack.builtinblocks.config.js,target=webpack.builtinblocks.config.js \ + --mount=type=bind,from=edx-platform,source=/cms/djangoapps/pipeline_js/js/xmodule.js,target=cms/djangoapps/pipeline_js/js/xmodule.js \ + --mount=type=bind,from=edx-platform,source=/cms/static,target=cms/static \ + --mount=type=bind,from=edx-platform,source=/cms/templates/,target=cms/templates/ \ + --mount=type=bind,from=edx-platform,source=/common/static/js/,target=common/static/js \ + --mount=type=bind,from=edx-platform,source=/common/static/common,target=common/static/common \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/discussion/static,target=lms/djangoapps/discussion/static \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/instructor/static,target=lms/djangoapps/instructor/static \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/support/static/support,target=lms/djangoapps/support/static/support \ + --mount=type=bind,from=edx-platform,source=/lms/djangoapps/teams/static,target=lms/djangoapps/teams/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/,target=lms/static/ \ + --mount=type=bind,from=edx-platform,source=/lms/templates/,target=lms/templates/ \ + --mount=type=bind,from=edx-platform,source=/openedx/features/announcements/static,target=openedx/features/announcements/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_bookmarks/static,target=openedx/features/course_bookmarks/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_experience/static,target=openedx/features/course_experience/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/course_search/static,target=openedx/features/course_search/static \ + --mount=type=bind,from=edx-platform,source=/openedx/features/learner_profile/static,target=openedx/features/learner_profile/static \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + --mount=type=bind,from=edx-platform,source=/xmodule/js,target=xmodule/js \ + npm run webpack-dev + + +########################################################################################################################### +############## CSS: PRODUCTION +########################################################################################################################### +FROM requirements-frontend as css-production + +ENV PATH ./node_modules/.bin:${PATH} + +# Compile default CSS +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/scripts/compile_sass.py,target=scripts/compile_sass.py \ + --mount=type=bind,from=edx-platform,source=/common/static,target=common/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass,target=lms/static/sass \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass/partials,target=lms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/lms/static/certificates/sass,target=lms/static/certificates/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass,target=cms/static/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass/partials,target=cms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + npm run compile-sass -- --skip-themes + +# Compile themed CSS +ENV EDX_PLATFORM_THEME_DIRS=/openedx/themes +COPY --chown=app:app ./themes/ /openedx/themes/ +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/scripts/compile_sass.py,target=scripts/compile_sass.py \ + --mount=type=bind,from=edx-platform,source=/common/static,target=common/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass,target=lms/static/sass \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass/partials,target=lms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/lms/static/certificates/sass,target=lms/static/certificates/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass,target=cms/static/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass/partials,target=cms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + npm run compile-sass -- --skip-default + + +########################################################################################################################### +############## CSS: DEVELOPMENT +########################################################################################################################### +FROM requirements-frontend as css-development + +ENV PATH ./node_modules/.bin:${PATH} + +# Compile default CSS +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/scripts/compile_sass.py,target=scripts/compile_sass.py \ + --mount=type=bind,from=edx-platform,source=/common/static,target=common/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass,target=lms/static/sass \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass/partials,target=lms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/lms/static/certificates/sass,target=lms/static/certificates/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass,target=cms/static/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass/partials,target=cms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + npm run compile-sass -- --skip-themes --env=dev + +# Compile themed CSS +ENV EDX_PLATFORM_THEME_DIRS=/openedx/themes +COPY --chown=app:app ./themes/ /openedx/themes/ +RUN --mount=type=bind,from=edx-platform,source=/package.json,target=package.json \ + --mount=type=bind,from=edx-platform,source=/scripts/compile_sass.py,target=scripts/compile_sass.py \ + --mount=type=bind,from=edx-platform,source=/common/static,target=common/static \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass,target=lms/static/sass \ + --mount=type=bind,from=edx-platform,source=/lms/static/sass/partials,target=lms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/lms/static/certificates/sass,target=lms/static/certificates/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass,target=cms/static/sass \ + --mount=type=bind,from=edx-platform,source=/cms/static/sass/partials,target=cms/static/sass/partials \ + --mount=type=bind,from=edx-platform,source=/xmodule/assets,target=xmodule/assets \ + npm run compile-sass -- --skip-default --env=dev + + +########################################################################################################################### +############## APPLICATION REQUIREMENTS +########################################################################################################################### +FROM requirements-frontend as requirements-application # Install base requirements -RUN {% if is_buildkit_enabled() %}--mount=type=bind,from=edx-platform,source=/requirements/edx/base.txt,target=/openedx/edx-platform/requirements/edx/base.txt \ - --mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install -r /openedx/edx-platform/requirements/edx/base.txt +RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/base.txt,target=/openedx/edx-platform/requirements/edx/base.txt \ + --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + pip install -r /openedx/edx-platform/requirements/edx/base.txt # Install extra requirements -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install \ +RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + pip install \ # Use redis as a django cache https://pypi.org/project/django-redis/ django-redis==5.2.0 \ # uwsgi server https://pypi.org/project/uWSGI/ @@ -103,37 +304,48 @@ RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip, # Install private requirements: this is useful for installing custom xblocks. COPY ./requirements/ /openedx/requirements -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}cd /openedx/requirements/ \ - && touch ./private.txt \ - && pip install -r ./private.txt +RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + cd /openedx/requirements/ \ + && touch ./private.txt \ + && pip install -r ./private.txt -{% for extra_requirements in OPENEDX_EXTRA_PIP_REQUIREMENTS %}RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install '{{ extra_requirements }}' +{% for extra_requirements in OPENEDX_EXTRA_PIP_REQUIREMENTS %}RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared pip install '{{ extra_requirements }}' {% endfor %} -###### Install nodejs with nodeenv in /openedx/nodeenv -FROM python as nodejs-requirements -ENV PATH /openedx/nodeenv/bin:/openedx/venv/bin:${PATH} -# Install nodeenv with the version provided by edx-platform -# https://github.com/openedx/edx-platform/blob/master/requirements/edx/base.txt -# https://github.com/pyenv/pyenv/releases -RUN pip install nodeenv==1.7.0 -RUN nodeenv /openedx/nodeenv --node=16.14.0 --prebuilt +########################################################################################################################### +############## PRODUCTION REQUIREMENTS +############## Simply take the general requirements, and remove everything related to libsass. +############## Alternatively, we could have based requirements-application on minimal, thus keeping libsass out of it, +############## BUT that would have required requirements-development to recompile libsass, which is slow. +########################################################################################################################### +FROM requirements-application as requirements-production -# Install nodejs requirements -ARG NPM_REGISTRY={{ NPM_REGISTRY }} -WORKDIR /openedx/edx-platform -RUN {% if is_buildkit_enabled() %}--mount=type=bind,from=edx-platform,source=/package.json,target=/openedx/edx-platform/package.json \ - --mount=type=bind,from=edx-platform,source=/package-lock.json,target=/openedx/edx-platform/package-lock.json \ - --mount=type=bind,from=edx-platform,source=/scripts/copy-node-modules.sh,target=/openedx/edx-platform/scripts/copy-node-modules.sh \ - --mount=type=cache,target=/root/.npm,sharing=shared {% endif %}npm clean-install --no-audit --registry=$NPM_REGISTRY +RUN rm -r /openedx/venv/lib/python3.8/site-packages/*sass* -###### Production image with system and python requirements -FROM minimal as production + +########################################################################################################################### +############## DEVELOPMENT REQUIREMENTS +########################################################################################################################### +FROM requirements-application as requirements-development + +# Install dev requirements +RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/development.txt,target=/openedx/edx-platform/requirements/edx/development.txt \ + --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + pip install -r /openedx/edx-platform/requirements/edx/development.txt + + +########################################################################################################################### +############## APPLICATION +############## Application image with system and python requirements. +############## Basis for final prod and dev images. +########################################################################################################################### +FROM minimal as application # Install system requirements -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked {% endif %}apt update \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt update \ && apt install -y gettext gfortran graphviz graphviz-dev libffi-dev libfreetype6-dev libgeos-dev libjpeg8-dev liblapack-dev libmysqlclient-dev libpng-dev libsqlite3-dev libxmlsec1-dev lynx mysql-client ntp pkg-config rdfind # From then on, run as unprivileged "app" user @@ -144,14 +356,15 @@ RUN useradd --home-dir /openedx --create-home --shell /bin/bash --uid ${APP_USER USER ${APP_USER_ID} # https://hub.docker.com/r/powerman/dockerize/tags -COPY {% if is_buildkit_enabled() %}--link {% endif %}--from=docker.io/powerman/dockerize:0.19.0 /usr/local/bin/dockerize /usr/local/bin/dockerize +COPY --link --from=docker.io/powerman/dockerize:0.19.0 /usr/local/bin/dockerize /usr/local/bin/dockerize COPY --chown=app:app --from=edx-platform / /openedx/edx-platform COPY --chown=app:app --from=locales /openedx/locale /openedx/locale COPY --chown=app:app --from=python /opt/pyenv /opt/pyenv -COPY --chown=app:app --from=python-requirements /openedx/venv /openedx/venv -COPY --chown=app:app --from=python-requirements /openedx/requirements /openedx/requirements -COPY --chown=app:app --from=nodejs-requirements /openedx/nodeenv /openedx/nodeenv -COPY --chown=app:app --from=nodejs-requirements /openedx/edx-platform/node_modules /openedx/node_modules +COPY --chown=app:app --from=requirements-production /openedx/venv /openedx/venv +COPY --chown=app:app --from=requirements-production /openedx/requirements /openedx/requirements +COPY --chown=app:app --from=requirements-frontend /openedx/nodeenv /openedx/nodeenv +COPY --chown=app:app --from=requirements-frontend /openedx/edx-platform/node_modules /openedx/node_modules +COPY --chown=app:app --from=requirements-frontend /openedx/edx-platform/common/static/node_copies /openedx/edx-platform/common/static/node_copies # Symlink node_modules such that we can bind-mount the edx-platform repository RUN ln -s /openedx/node_modules /openedx/edx-platform/node_modules @@ -191,30 +404,6 @@ COPY --chown=app:app ./bin /openedx/bin RUN chmod a+x /openedx/bin/* ENV PATH /openedx/bin:${PATH} -{{ patch("openedx-dockerfile-pre-assets") }} - -# Collect production assets. By default, only assets from the default theme -# will be processed. This makes the docker image lighter and faster to build. -# Only the custom themes added to /openedx/themes will be compiled. -# Here, we don't run "paver update_assets" which is slow, compiles all themes -# and requires a complex settings file. Instead, we decompose the commands -# and run each one individually to collect the production static assets to -# /openedx/staticfiles. -ENV NO_PYTHON_UNINSTALL 1 -ENV NO_PREREQ_INSTALL 1 -# We need to rely on a separate openedx-assets command to accelerate asset processing. -# For instance, we don't want to run all steps of asset collection every time the theme -# is modified. -RUN openedx-assets xmodule \ - && openedx-assets npm \ - && openedx-assets webpack --env=prod \ - && openedx-assets common -COPY --chown=app:app ./themes/ /openedx/themes/ -RUN openedx-assets themes \ - && openedx-assets collect --settings=tutor.assets \ - # De-duplicate static assets with symlinks - && rdfind -makesymlinks true -followsymlinks true /openedx/staticfiles/ - # Create a data directory, which might be used (or not) RUN mkdir /openedx/data @@ -227,37 +416,82 @@ RUN echo \ # service variant is "lms" or "cms" ENV SERVICE_VARIANT lms -ENV DJANGO_SETTINGS_MODULE lms.envs.tutor.production {{ patch("openedx-dockerfile") }} EXPOSE 8000 -###### Intermediate image with dev/test dependencies -FROM production as development + +########################################################################################################################### +############## PRODUCTION +########################################################################################################################### +FROM application as production + +# Copy in and collect production static assets. +COPY --chown=app:app --from=bundles-production /openedx/staticfiles /openedx/staticfiles +COPY --chown=app:app --from=bundles-production /openedx/edx-platform/common/static/bundles /openedx/edx-platform/common/static/bundles +COPY --chown=app:app --from=css-production /openedx/edx-platform/lms/static/css /openedx/edx-platform/lms/static/css +COPY --chown=app:app --from=css-production /openedx/edx-platform/lms/static/certificates/css /openedx/edx-platform/lms/static/certificates/css +COPY --chown=app:app --from=css-production /openedx/edx-platform/cms/static/css /openedx/edx-platform/cms/static/css +RUN ./manage.py lms collectstatic --noinput --settings=tutor.assets +RUN ./manage.py cms collectstatic --noinput --settings=tutor.assets + +# De-dupe static assets with synlinks +RUN rdfind -makesymlinks true -followsymlinks true /openedx/staticfiles/ + +# Default amount of uWSGI processes +ENV UWSGI_WORKERS=2 + +# Copy the default uWSGI configuration +COPY --chown=app:app settings/uwsgi.ini . + +# Default django settings +ENV DJANGO_SETTINGS_MODULE lms.envs.tutor.production + +# Run server +CMD uwsgi uwsgi.ini + +{{ patch("openedx-dockerfile-final") }} + + +########################################################################################################################### +############## DEVELOPMENT +########################################################################################################################### +FROM application as development # Install useful system requirements (as root) USER root -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked {% endif %}apt update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt update && \ apt install -y vim iputils-ping dnsutils telnet USER app -# Install dev python requirements -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install -r requirements/edx/development.txt +# Copy in dev python requirements +COPY --chown=app:app --from=requirements-development /openedx/venv /openedx/venv + # https://pypi.org/project/ipdb/ # https://pypi.org/project/ipython -RUN {% if is_buildkit_enabled() %}--mount=type=cache,target=/openedx/.cache/pip,sharing=shared {% endif %}pip install ipdb==0.13.13 ipython==8.12.0 +RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \ + pip install ipdb==0.13.13 ipython==8.12.0 # Add ipdb as default PYTHONBREAKPOINT ENV PYTHONBREAKPOINT=ipdb.set_trace -# Recompile static assets: in development mode all static assets are stored in edx-platform, -# and the location of these files is stored in webpack-stats.json. If we don't recompile -# static assets, then production assets will be served instead. -RUN rm -r /openedx/staticfiles && \ - mkdir /openedx/staticfiles && \ - openedx-assets webpack --env=dev +# Copy in development static assets. +# In development mode, edx-platform expects the files to be in the repo, and pointed to by webpack-stats.json... +COPY --chown=app:app --from=bundles-development /openedx/staticfiles/webpack-stats.json /openedx/staticfiles/webpack-stats.json +COPY --chown=app:app --from=bundles-development /openedx/staticfiles/studio/webpack-stats.json /openedx/staticfiles/studio/webpack-stats.json + +# ...however, we instead copy the files into the /openedx/devcache directory; otherwise, bind-mounting edx-platform +# would clobber them and force the user to rebuild assets. +COPY --chown=app:app --from=bundles-development /openedx/edx-platform/common/static/bundles /openedx/devcache/common/static/bundles +COPY --chown=app:app --from=css-development /openedx/edx-platform/lms/static/css /openedx/devcache/lms/static/css +COPY --chown=app:app --from=css-development /openedx/edx-platform/lms/static/certificates/css /openedx/devcache/lms/static/certificates/css +COPY --chown=app:app --from=css-development /openedx/edx-platform/cms/static/css /openedx/devcache/cms/static/css + +# Create links for static dirs from edx-platform to devcache. +RUN link-static-to-devcache {{ patch("openedx-dev-dockerfile-post-python-requirements") }} @@ -265,18 +499,3 @@ RUN rm -r /openedx/staticfiles && \ ENV DJANGO_SETTINGS_MODULE lms.envs.tutor.development CMD ./manage.py $SERVICE_VARIANT runserver 0.0.0.0:8000 - -###### Final image with production cmd -FROM production as final - -# Default amount of uWSGI processes -ENV UWSGI_WORKERS=2 - -# Copy the default uWSGI configuration -COPY --chown=app:app settings/uwsgi.ini . - -# Run server -CMD uwsgi uwsgi.ini - -{{ patch("openedx-dockerfile-final") }} - diff --git a/tutor/templates/build/openedx/bin/link-static-to-devcache b/tutor/templates/build/openedx/bin/link-static-to-devcache new file mode 100755 index 00000000000..819bf4673d7 --- /dev/null +++ b/tutor/templates/build/openedx/bin/link-static-to-devcache @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +cd /openedx/edx-platform + +for path in common/static/bundles lms/static/css lms/static/certificates/css cms/static/css ; do + if [[ -L "$path" ]] ; then + rm -rf "$path" + elif [[ -e "$path" ]] ; then + mv "$path" "${path}.bak" + fi + ln -s "/openedx/devcache/$path" "$path" +done diff --git a/tutor/templates/jobs/init/mounted-edx-platform.sh b/tutor/templates/jobs/init/mounted-edx-platform.sh index 9516654ff58..f5b1dbbe88b 100644 --- a/tutor/templates/jobs/init/mounted-edx-platform.sh +++ b/tutor/templates/jobs/init/mounted-edx-platform.sh @@ -19,8 +19,8 @@ pip install -e . # Regenerate node_modules npm clean-install -# Regenerate static assets. -openedx-assets build --env=dev +# Link to static assets in dev image. +link-static-to-devcache set -x echo "Done setting up bind-mounted edx-platform."