diff --git a/jupyterhub/tensorflow-gpu/Dockerfile b/jupyterhub/tensorflow-gpu/Dockerfile index 53f65fd..082fb8b 100644 --- a/jupyterhub/tensorflow-gpu/Dockerfile +++ b/jupyterhub/tensorflow-gpu/Dockerfile @@ -1,9 +1,15 @@ # Base image: https://github.com/jupyter/docker-stacks/blob/74bbd0bffc3b444e2d65279739bc2d681b6199e2/images/docker-stacks-foundation/Dockerfile ARG ROOT_CONTAINER=tensorflow/tensorflow:2.14.0-gpu + +ARG FOUNDATION_CONTAINER=quay.io/jupyter/docker-stacks-foundation:4d70cf8da953 ARG BASE_NOTEBOOK=quay.io/jupyter/base-notebook:b86753318aa1 +ARG MIN_NOTEBOOK +FROM ${FOUNDATION_CONTAINER} as foundation FROM ${BASE_NOTEBOOK} as base_notebook + + FROM $ROOT_CONTAINER LABEL maintainer="Antonio J. Chaves " @@ -12,27 +18,58 @@ ARG NB_USER="ertis" ARG NB_UID="1000" ARG NB_GID="100" +# Configure environment +ENV CONDA_DIR=/opt/conda \ + SHELL=/bin/bash \ + NB_USER="${NB_USER}" \ + NB_UID=${NB_UID} \ + NB_GID=${NB_GID} \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 +ENV PATH="${CONDA_DIR}/bin:${PATH}" \ + HOME="/home/${NB_USER}" + SHELL ["/bin/bash", "-o", "pipefail", "-c"] USER root +COPY --from=foundation /usr/local/bin/fix-permissions /usr/local/bin/ +COPY --from=foundation --chown="${NB_UID}:${NB_GID}" "${CONDA_DIR}/.condarc/initial-condarc" "${CONDA_DIR}/.condarc/" +COPY --from=foundation /usr/local/bin/run-hooks.sh /usr/local/bin/ +COPY --from=foundation /usr/local/bin/start.sh /usr/local/bin/ COPY --from=base_notebook /usr/local/bin/start-notebook.sh /usr/local/bin/ COPY --from=base_notebook /usr/local/bin/start-singleuser.sh /usr/local/bin/ COPY --from=base_notebook /etc/jupyter/jupyter_server_config.py /etc/jupyter/ - - +COPY --from=base_notebook /etc/jupyter/docker_healthcheck.py /etc/jupyter/ ENV DEBIAN_FRONTEND noninteractive + RUN apt-get update --yes && \ apt-get upgrade --yes && \ apt-get install --yes --no-install-recommends \ bzip2 \ + curl \ + git \ + tzdata \ + openssh-client \ + less \ + texlive-xetex \ + texlive-fonts-recommended \ + texlive-plain-generic \ + xclip \ + git \ + nano \ + bat \ + fonts-liberation \ + pandoc \ + run-one && \ + iputils-ping \ ca-certificates \ locales \ sudo \ - iputils-ping \ gpg \ tini \ wget && \ @@ -41,20 +78,14 @@ RUN apt-get update --yes && \ locale-gen -# Configure environment -ENV CONDA_DIR=/opt/conda \ - SHELL=/bin/bash \ - NB_USER="${NB_USER}" \ - NB_UID=${NB_UID} \ - NB_GID=${NB_GID} \ - LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 \ - LANGUAGE=en_US.UTF-8 -ENV PATH="${CONDA_DIR}/bin:${PATH}" \ - HOME="/home/${NB_USER}" +RUN mkdir -p /etc/apt/keyrings && \ + wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list && \ + chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list && \ + apt update -y && \ + apt install -y eza # Copy a script that we will use to correct permissions after running certain commands -COPY fix-permissions /usr/local/bin/fix-permissions RUN chmod a+rx /usr/local/bin/fix-permissions # Enable prompt color in the skeleton .bashrc before creating the default NB_USER @@ -125,10 +156,7 @@ RUN set -x && \ # Configure container startup ENTRYPOINT ["tini", "-g", "--"] -CMD ["start.sh"] - -# Copy local files as late as possible to avoid cache busting -COPY run-hooks.sh start.sh /usr/local/bin/ +# CMD ["start.sh"] USER root @@ -136,48 +164,6 @@ USER root RUN mkdir /usr/local/bin/start-notebook.d && \ mkdir /usr/local/bin/before-notebook.d - -RUN apt-get update --yes && \ - apt-get upgrade --yes && \ - apt-get install --yes --no-install-recommends \ - bzip2 \ - curl \ - tzdata \ - unzip \ - vim-tiny \ - openssh-client \ - less \ - texlive-xetex \ - texlive-fonts-recommended \ - texlive-plain-generic \ - xclip \ - git \ - nano \ - bat \ - ca-certificates \ - locales \ - sudo \ - iputils-ping \ - gpg \ - tini \ - fonts-liberation \ - # - pandoc is used to convert notebooks to html files - # it's not present in aarch64 ubuntu image, so we install it here - pandoc \ - # - run-one - a wrapper script that runs no more - # than one unique instance of some command with a unique set of arguments, - # we use `run-one-constantly` to support `RESTARTABLE` option - run-one && \ - wget && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /etc/apt/keyrings && \ - wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg && \ - echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list && \ - chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list && \ - apt update -y && \ - apt install -y eza - USER ${NB_UID} # Install JupyterLab, Jupyter Notebook, JupyterHub and NBClassic @@ -206,13 +192,17 @@ EXPOSE $JUPYTER_PORT # Configure container startup CMD ["start-notebook.sh"] -# Copy local files as late as possible to avoid cache busting -COPY start-notebook.sh start-singleuser.sh /usr/local/bin/ -COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ +# Fix permissions on /etc/jupyter as root +USER root +RUN fix-permissions /etc/jupyter/ +# HEALTHCHECK documentation: https://docs.docker.com/engine/reference/builder/#healthcheck +# This healtcheck works well for `lab`, `notebook`, `nbclassic`, `server` and `retro` jupyter commands +# https://github.com/jupyter/docker-stacks/issues/915#issuecomment-1068528799 +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \ + CMD /etc/jupyter/docker_healthcheck.py || exit 1 # Switch back to jovyan to avoid accidental container runs as root USER ${NB_UID} -WORKDIR "${HOME}" - +WORKDIR "${HOME}" \ No newline at end of file diff --git a/jupyterhub/tensorflow-gpu/fix-permissions b/jupyterhub/tensorflow-gpu/fix-permissions deleted file mode 100644 index d167578..0000000 --- a/jupyterhub/tensorflow-gpu/fix-permissions +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# set permissions on a directory -# after any installation, if a directory needs to be (human) user-writable, -# run this script on it. -# It will make everything in the directory owned by the group ${NB_GID} -# and writable by that group. -# Deployments that want to set a specific user id can preserve permissions -# by adding the `--group-add users` line to `docker run`. - -# uses find to avoid touching files that already have the right permissions, -# which would cause massive image explosion - -# right permissions are: -# group=${NB_GID} -# AND permissions include group rwX (directory-execute) -# AND directories have setuid,setgid bits set - -set -e - -for d in "$@"; do - find "${d}" \ - ! \( \ - -group "${NB_GID}" \ - -a -perm -g+rwX \ - \) \ - -exec chgrp "${NB_GID}" -- {} \+ \ - -exec chmod g+rwX -- {} \+ - # setuid, setgid *on directories only* - find "${d}" \ - \( \ - -type d \ - -a ! -perm -6000 \ - \) \ - -exec chmod +6000 -- {} \+ -done diff --git a/jupyterhub/tensorflow-gpu/initial-condarc b/jupyterhub/tensorflow-gpu/initial-condarc deleted file mode 100644 index 383aad3..0000000 --- a/jupyterhub/tensorflow-gpu/initial-condarc +++ /dev/null @@ -1,6 +0,0 @@ -# Conda configuration see https://conda.io/projects/conda/en/latest/configuration.html - -auto_update_conda: false -show_channel_urls: true -channels: - - conda-forge diff --git a/jupyterhub/tensorflow-gpu/run-hooks.sh b/jupyterhub/tensorflow-gpu/run-hooks.sh deleted file mode 100644 index d5dc28e..0000000 --- a/jupyterhub/tensorflow-gpu/run-hooks.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -# The run-hooks.sh script looks for *.sh scripts to source -# and executable files to run within a passed directory - -if [ "$#" -ne 1 ]; then - echo "Should pass exactly one directory" - return 1 -fi - -if [[ ! -d "${1}" ]] ; then - echo "Directory ${1} doesn't exist or is not a directory" - return 1 -fi - -echo "Running hooks in: ${1} as uid: $(id -u) gid: $(id -g)" -for f in "${1}/"*; do - # Hadling a case when the directory is empty - [ -e "${f}" ] || continue - case "${f}" in - *.sh) - echo "Sourcing shell script: ${f}" - # shellcheck disable=SC1090 - source "${f}" - # shellcheck disable=SC2181 - if [ $? -ne 0 ] ; then - echo "${f} has failed, continuing execution" - fi - ;; - *) - if [ -x "${f}" ] ; then - echo "Running executable: ${f}" - "${f}" - # shellcheck disable=SC2181 - if [ $? -ne 0 ] ; then - echo "${f} has failed, continuing execution" - fi - else - echo "Ignoring non-executable: ${f}" - fi - ;; - esac -done -echo "Done running hooks in: ${1}" diff --git a/jupyterhub/tensorflow-gpu/start.sh b/jupyterhub/tensorflow-gpu/start.sh deleted file mode 100644 index ac4f02d..0000000 --- a/jupyterhub/tensorflow-gpu/start.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/bin/bash -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -set -e - -# The _log function is used for everything this script wants to log. It will -# always log errors and warnings, but can be silenced for other messages -# by setting JUPYTER_DOCKER_STACKS_QUIET environment variable. -_log () { - if [[ "$*" == "ERROR:"* ]] || [[ "$*" == "WARNING:"* ]] || [[ "${JUPYTER_DOCKER_STACKS_QUIET}" == "" ]]; then - echo "$@" - fi -} -_log "Entered start.sh with args:" "$@" - -# A helper function to unset env vars listed in the value of the env var -# JUPYTER_ENV_VARS_TO_UNSET. -unset_explicit_env_vars () { - if [ -n "${JUPYTER_ENV_VARS_TO_UNSET}" ]; then - for env_var_to_unset in $(echo "${JUPYTER_ENV_VARS_TO_UNSET}" | tr ',' ' '); do - _log "Unset ${env_var_to_unset} due to JUPYTER_ENV_VARS_TO_UNSET" - unset "${env_var_to_unset}" - done - unset JUPYTER_ENV_VARS_TO_UNSET - fi -} - - -# Default to starting bash if no command was specified -if [ $# -eq 0 ]; then - cmd=( "bash" ) -else - cmd=( "$@" ) -fi - -# NOTE: This hook will run as the user the container was started with! -# shellcheck disable=SC1091 -source /usr/local/bin/run-hooks.sh /usr/local/bin/start-notebook.d - -# If the container started as the root user, then we have permission to refit -# the jovyan user, and ensure file permissions, grant sudo rights, and such -# things before we run the command passed to start.sh as the desired user -# (NB_USER). -# -if [ "$(id -u)" == 0 ] ; then - # Environment variables: - # - NB_USER: the desired username and associated home folder - # - NB_UID: the desired user id - # - NB_GID: a group id we want our user to belong to - # - NB_GROUP: a group name we want for the group - # - GRANT_SUDO: a boolean ("1" or "yes") to grant the user sudo rights - # - CHOWN_HOME: a boolean ("1" or "yes") to chown the user's home folder - # - CHOWN_EXTRA: a comma separated list of paths to chown - # - CHOWN_HOME_OPTS / CHOWN_EXTRA_OPTS: arguments to the chown commands - - # Refit the jovyan user to the desired the user (NB_USER) - if id jovyan &> /dev/null ; then - if ! usermod --home "/home/${NB_USER}" --login "${NB_USER}" jovyan 2>&1 | grep "no changes" > /dev/null; then - _log "Updated the jovyan user:" - _log "- username: jovyan -> ${NB_USER}" - _log "- home dir: /home/jovyan -> /home/${NB_USER}" - fi - elif ! id -u "${NB_USER}" &> /dev/null; then - _log "ERROR: Neither the jovyan user or '${NB_USER}' exists. This could be the result of stopping and starting, the container with a different NB_USER environment variable." - exit 1 - fi - # Ensure the desired user (NB_USER) gets its desired user id (NB_UID) and is - # a member of the desired group (NB_GROUP, NB_GID) - if [ "${NB_UID}" != "$(id -u "${NB_USER}")" ] || [ "${NB_GID}" != "$(id -g "${NB_USER}")" ]; then - _log "Update ${NB_USER}'s UID:GID to ${NB_UID}:${NB_GID}" - # Ensure the desired group's existence - if [ "${NB_GID}" != "$(id -g "${NB_USER}")" ]; then - groupadd --force --gid "${NB_GID}" --non-unique "${NB_GROUP:-${NB_USER}}" - fi - # Recreate the desired user as we want it - userdel "${NB_USER}" - useradd --no-log-init --home "/home/${NB_USER}" --shell /bin/bash --uid "${NB_UID}" --gid "${NB_GID}" --groups 100 "${NB_USER}" - fi - - # Move or symlink the jovyan home directory to the desired users home - # directory if it doesn't already exist, and update the current working - # directory to the new location if needed. - if [[ "${NB_USER}" != "jovyan" ]]; then - if [[ ! -e "/home/${NB_USER}" ]]; then - _log "Attempting to copy /home/jovyan to /home/${NB_USER}..." - mkdir "/home/${NB_USER}" - if cp -a /home/jovyan/. "/home/${NB_USER}/"; then - _log "Success!" - else - _log "Failed to copy data from /home/jovyan to /home/${NB_USER}!" - _log "Attempting to symlink /home/jovyan to /home/${NB_USER}..." - if ln -s /home/jovyan "/home/${NB_USER}"; then - _log "Success creating symlink!" - else - _log "ERROR: Failed copy data from /home/jovyan to /home/${NB_USER} or to create symlink!" - exit 1 - fi - fi - fi - # Ensure the current working directory is updated to the new path - if [[ "${PWD}/" == "/home/jovyan/"* ]]; then - new_wd="/home/${NB_USER}/${PWD:13}" - _log "Changing working directory to ${new_wd}" - cd "${new_wd}" - fi - fi - - # Optionally ensure the desired user get filesystem ownership of it's home - # folder and/or additional folders - if [[ "${CHOWN_HOME}" == "1" || "${CHOWN_HOME}" == "yes" ]]; then - _log "Ensuring /home/${NB_USER} is owned by ${NB_UID}:${NB_GID} ${CHOWN_HOME_OPTS:+(chown options: ${CHOWN_HOME_OPTS})}" - # shellcheck disable=SC2086 - chown ${CHOWN_HOME_OPTS} "${NB_UID}:${NB_GID}" "/home/${NB_USER}" - fi - if [ -n "${CHOWN_EXTRA}" ]; then - for extra_dir in $(echo "${CHOWN_EXTRA}" | tr ',' ' '); do - _log "Ensuring ${extra_dir} is owned by ${NB_UID}:${NB_GID} ${CHOWN_EXTRA_OPTS:+(chown options: ${CHOWN_EXTRA_OPTS})}" - # shellcheck disable=SC2086 - chown ${CHOWN_EXTRA_OPTS} "${NB_UID}:${NB_GID}" "${extra_dir}" - done - fi - - # Update potentially outdated environment variables since image build - export XDG_CACHE_HOME="/home/${NB_USER}/.cache" - - # Prepend ${CONDA_DIR}/bin to sudo secure_path - sed -r "s#Defaults\s+secure_path\s*=\s*\"?([^\"]+)\"?#Defaults secure_path=\"${CONDA_DIR}/bin:\1\"#" /etc/sudoers | grep secure_path > /etc/sudoers.d/path - - # Optionally grant passwordless sudo rights for the desired user - if [[ "$GRANT_SUDO" == "1" || "$GRANT_SUDO" == "yes" ]]; then - _log "Granting ${NB_USER} passwordless sudo rights!" - echo "${NB_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/added-by-start-script - fi - - # NOTE: This hook is run as the root user! - # shellcheck disable=SC1091 - source /usr/local/bin/run-hooks.sh /usr/local/bin/before-notebook.d - unset_explicit_env_vars - - _log "Running as ${NB_USER}:" "${cmd[@]}" - exec sudo --preserve-env --set-home --user "${NB_USER}" \ - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" \ - PATH="${PATH}" \ - PYTHONPATH="${PYTHONPATH:-}" \ - "${cmd[@]}" - # Notes on how we ensure that the environment that this container is started - # with is preserved (except vars listed in JUPYTER_ENV_VARS_TO_UNSET) when - # we transition from running as root to running as NB_USER. - # - # - We use `sudo` to execute the command as NB_USER. What then - # happens to the environment will be determined by configuration in - # /etc/sudoers and /etc/sudoers.d/* as well as flags we pass to the sudo - # command. The behavior can be inspected with `sudo -V` run as root. - # - # ref: `man sudo` https://linux.die.net/man/8/sudo - # ref: `man sudoers` https://www.sudo.ws/docs/man/sudoers.man/ - # - # - We use the `--preserve-env` flag to pass through most environment - # variables, but understand that exceptions are caused by the sudoers - # configuration: `env_delete` and `env_check`. - # - # - We use the `--set-home` flag to set the HOME variable appropriately. - # - # - To reduce the default list of variables deleted by sudo, we could have - # used `env_delete` from /etc/sudoers. It has higher priority than the - # `--preserve-env` flag and the `env_keep` configuration. - # - # - We preserve LD_LIBRARY_PATH, PATH and PYTHONPATH explicitly. Note however that sudo - # resolves `${cmd[@]}` using the "secure_path" variable we modified - # above in /etc/sudoers.d/path. Thus PATH is irrelevant to how the above - # sudo command resolves the path of `${cmd[@]}`. The PATH will be relevant - # for resolving paths of any subprocesses spawned by `${cmd[@]}`. - -# The container didn't start as the root user, so we will have to act as the -# user we started as. -else - # Warn about misconfiguration of: granting sudo rights - if [[ "${GRANT_SUDO}" == "1" || "${GRANT_SUDO}" == "yes" ]]; then - _log "WARNING: container must be started as root to grant sudo permissions!" - fi - - JOVYAN_UID="$(id -u jovyan 2>/dev/null)" # The default UID for the jovyan user - JOVYAN_GID="$(id -g jovyan 2>/dev/null)" # The default GID for the jovyan user - - # Attempt to ensure the user uid we currently run as has a named entry in - # the /etc/passwd file, as it avoids software crashing on hard assumptions - # on such entry. Writing to the /etc/passwd was allowed for the root group - # from the Dockerfile during build. - # - # ref: https://github.com/jupyter/docker-stacks/issues/552 - if ! whoami &> /dev/null; then - _log "There is no entry in /etc/passwd for our UID=$(id -u). Attempting to fix..." - if [[ -w /etc/passwd ]]; then - _log "Renaming old jovyan user to nayvoj ($(id -u jovyan):$(id -g jovyan))" - - # We cannot use "sed --in-place" since sed tries to create a temp file in - # /etc/ and we may not have write access. Apply sed on our own temp file: - sed --expression="s/^jovyan:/nayvoj:/" /etc/passwd > /tmp/passwd - echo "${NB_USER}:x:$(id -u):$(id -g):,,,:/home/jovyan:/bin/bash" >> /tmp/passwd - cat /tmp/passwd > /etc/passwd - rm /tmp/passwd - - _log "Added new ${NB_USER} user ($(id -u):$(id -g)). Fixed UID!" - - if [[ "${NB_USER}" != "jovyan" ]]; then - _log "WARNING: user is ${NB_USER} but home is /home/jovyan. You must run as root to rename the home directory!" - fi - else - _log "WARNING: unable to fix missing /etc/passwd entry because we don't have write permission. Try setting gid=0 with \"--user=$(id -u):0\"." - fi - fi - - # Warn about misconfiguration of: desired username, user id, or group id. - # A misconfiguration occurs when the user modifies the default values of - # NB_USER, NB_UID, or NB_GID, but we cannot update those values because we - # are not root. - if [[ "${NB_USER}" != "jovyan" && "${NB_USER}" != "$(id -un)" ]]; then - _log "WARNING: container must be started as root to change the desired user's name with NB_USER=\"${NB_USER}\"!" - fi - if [[ "${NB_UID}" != "${JOVYAN_UID}" && "${NB_UID}" != "$(id -u)" ]]; then - _log "WARNING: container must be started as root to change the desired user's id with NB_UID=\"${NB_UID}\"!" - fi - if [[ "${NB_GID}" != "${JOVYAN_GID}" && "${NB_GID}" != "$(id -g)" ]]; then - _log "WARNING: container must be started as root to change the desired user's group id with NB_GID=\"${NB_GID}\"!" - fi - - # Warn if the user isn't able to write files to ${HOME} - if [[ ! -w /home/jovyan ]]; then - _log "WARNING: no write access to /home/jovyan. Try starting the container with group 'users' (100), e.g. using \"--group-add=users\"." - fi - - # NOTE: This hook is run as the user we started the container as! - # shellcheck disable=SC1091 - source /usr/local/bin/run-hooks.sh /usr/local/bin/before-notebook.d - unset_explicit_env_vars - - _log "Executing the command:" "${cmd[@]}" - exec "${cmd[@]}" -fi