Skip to content

Commit 5e5642f

Browse files
committed
[AN-12] New terra base image
path updates Update base img Co-authored-by: Adam Nichols <[email protected]> python path
1 parent 7d4b885 commit 5e5642f

38 files changed

+3420
-10
lines changed

terra-base/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## 0.0.1 - 2025-08-01
2+
- Build off Ubuntu 22 with base image `gcr.io/deeplearning-platform-release/tf2-cu123.2-17.py310`
3+
4+
Image URL: `us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:0.0.1`

terra-base/Dockerfile

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Latest gpu-enabled base image on Ubuntu 24, 313MB
2+
FROM --platform=linux/amd64 nvidia/cuda:12.9.1-base-ubuntu24.04
3+
4+
LABEL maintainer="DSP Analysis Team <[email protected]>"
5+
6+
# want the command to fail due to an error at any stage in the pipe: https://github.com/hadolint/hadolint/wiki/DL4006
7+
SHELL ["/usr/bin/bash", "-o", "pipefail", "-c"]
8+
9+
#######################
10+
# General Environment Variables
11+
#######################
12+
ENV DEBIAN_FRONTEND=noninteractive
13+
ENV LC_ALL=en_US.UTF-8
14+
15+
# Version of python to be installed and used
16+
ENV PYTHON_VERSION=3.10
17+
# Paired conda installer
18+
ENV CONDA_INSTALLER=https://repo.anaconda.com/miniconda/Miniconda3-py310_23.5.1-0-Linux-x86_64.sh
19+
ENV JUPYTER_VERSION=5.7.2
20+
ENV NODE_MAJOR=20
21+
22+
###############
23+
# Install Prerequisites
24+
###############
25+
RUN apt-get update && apt-get install -yq --no-install-recommends \
26+
# basic necessities
27+
sudo \
28+
ca-certificates \
29+
curl \
30+
jq \
31+
# gnupg requirement
32+
gnupg \
33+
dirmngr \
34+
# useful utilities for debugging within docker itself
35+
nano \
36+
less \
37+
procps \
38+
lsb-release \
39+
# gcc compiler
40+
build-essential \
41+
locales \
42+
# for ssh-agent and ssh-add
43+
keychain \
44+
# extras \
45+
wget \
46+
bzip2 \
47+
git \
48+
# Uncomment en_US.UTF-8 for inclusion in generation
49+
&& sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen \
50+
# Generate locale
51+
&& locale-gen \
52+
&& apt-get clean \
53+
&& rm -rf /var/lib/apt/lists/*
54+
55+
##############################
56+
# Set up Node for Jupyterlab
57+
##############################
58+
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
59+
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
60+
61+
# Install Node >18 (needed for jupyterlab)
62+
RUN apt-get update && apt-get install -yq --no-install-recommends
63+
RUN mkdir -p /etc/apt/keyrings
64+
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
65+
66+
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
67+
RUN dpkg --remove --force-remove-reinstreq libnode-dev
68+
RUN apt-get update && apt-get install -f -yq nodejs
69+
70+
71+
#####################
72+
# Create the Jupyter User (what users will see in Terra)
73+
#####################
74+
# Create the jupyter user and give sudo permission
75+
ENV JUPYTER_USER=jupyter
76+
ENV JUPYTER_UID=1002
77+
78+
# Create the jupyter user home and add the user to the users group
79+
ENV JUPYTER_USER_HOME=/home/$JUPYTER_USER
80+
RUN useradd -m -s /bin/bash -d $JUPYTER_USER_HOME -N -u $JUPYTER_UID -g users $JUPYTER_USER
81+
82+
# We want to grant the jupyter user sudo permissions without password
83+
# so they can install the necessary packages that they want to use on the docker container
84+
RUN echo "$JUPYTER_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$JUPYTER_USER \
85+
&& chmod 0440 /etc/sudoers.d/$JUPYTER_USER
86+
87+
################
88+
# Create Welder user
89+
################
90+
# The welder uid must be consistent with the Welder docker definition here:
91+
# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala
92+
# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added
93+
# files display nicer when viewed in a terminal.
94+
ENV WELDER_USER=welder-user
95+
ENV WELDER_UID=1001
96+
RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER
97+
98+
############
99+
# Install R
100+
############
101+
RUN apt-get update && apt-get install -y r-base
102+
103+
################
104+
# Install Python
105+
################
106+
# Install Python 3.10 and add to system python path
107+
# Miniconda does come with it's own installation of python, but it
108+
# is cleaner to do a proper system instalation here and set the path
109+
#RUN apt-get update && apt-get install -y python$PYTHON_VERSION python3-pip
110+
#RUN update-alternatives --install /usr/bin/python python /usr/bin/python$PYTHON_VERSION 1 \
111+
# && update-alternatives --set python /usr/bin/python$PYTHON_VERSION
112+
113+
# Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python
114+
#ENV BASE_PYTHON_PATH=usr/bin/python3
115+
#
116+
###############
117+
# Install Miniconda
118+
###############
119+
## Note: CONDA is not used here to manage dependencies, but as a tool to manage python environments.
120+
## We want to store the user conda environments in a directory that will be in the persistent disk
121+
## Attention: If you change the Conda home location, please update conda_init.txt accordingly
122+
ENV CONDA_ENV_NAME=base-python${PYTHON_VERSION}
123+
ENV CONDA_ENV_HOME=$JUPYTER_USER_HOME/.envs/$CONDA_ENV_NAME
124+
RUN curl -so $JUPYTER_USER_HOME/miniconda.sh ${CONDA_INSTALLER} \
125+
&& chmod +x $JUPYTER_USER_HOME/miniconda.sh \
126+
&& $JUPYTER_USER_HOME/miniconda.sh -b -p $CONDA_ENV_HOME \
127+
&& rm $JUPYTER_USER_HOME/miniconda.sh
128+
ENV PATH="${PATH}:${CONDA_ENV_HOME}/bin"
129+
130+
## Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python
131+
ENV BASE_PYTHON_PATH=$CONDA_ENV_HOME/bin/python
132+
133+
# Tell conda to NOT write bite code (aka these.pyc files)
134+
ENV PYTHONDONTWRITEBYTECODE=true
135+
136+
###################################################
137+
# Set up the user to use the conda base environment
138+
###################################################
139+
## The user should have full access to the conda base environment, and can use it directly, or
140+
## create new conda environments on top of it. The important part is that jupyter IS NOT installed
141+
## in the base environment to provide isolation between the user environment, and the jupyter server
142+
## to avoid cross-contamination
143+
COPY conda-env.yml .
144+
RUN conda env update --prefix $CONDA_ENV_HOME --file conda-env.yml --prune \
145+
# Remove packages tarballs and python bytecode files from the image
146+
&& conda clean -afy \
147+
&& rm conda-env.yml \
148+
# Make sure the JUPYTER_USER is the owner of the folder where
149+
# the base conda is installed
150+
&& chown -R $JUPYTER_USER:users $JUPYTER_USER_HOME \
151+
# enable conda libmamba: https://www.anaconda.com/blog/a-faster-conda-for-a-growing-community \
152+
&& conda install -n base conda-libmamba-solver \
153+
&& conda config --set solver libmamba
154+
155+
# Add the user base conda environment as a jupyter kernel - this should be the default now
156+
# This commands activates the conda environment and then calls ipykernel from within
157+
# to install it as a kernel under the same name
158+
RUN conda run -p $CONDA_ENV_HOME python -m ipykernel install --name=$CONDA_ENV_NAME
159+
160+
# Prep the jupyter terminal to conda init and make sure the base conda environment is
161+
# activated and the name is displayed in the terminal prompt
162+
COPY conda_init.txt .
163+
RUN cat conda_init.txt >> $JUPYTER_USER_HOME/.bashrc && \
164+
printf "\nconda activate ${CONDA_ENV_HOME}" >> $JUPYTER_USER_HOME/.bashrc && \
165+
conda config --set env_prompt '({name})' && \
166+
source $JUPYTER_USER_HOME/.bashrc && \
167+
rm conda_init.txt
168+
169+
170+
##########
171+
# Setup UV
172+
##########
173+
# - Silence uv complaining about not being able to use hard links,
174+
# - tell uv to byte-compile packages for faster application startups,
175+
# - prevent uv from accidentally downloading isolated Python builds,
176+
# - specify Python version
177+
# - don't seed venv with wheel and setuptools, we need to install specific versions
178+
# - don't cache to keep the image size small
179+
# - use system python to avoid installing a new python version
180+
ENV UV_LINK_MODE=copy \
181+
UV_COMPILE_BYTECODE=1 \
182+
UV_PYTHON_DOWNLOADS=never \
183+
UV_PYTHON=$PYTHON_VERSION \
184+
UV_VENV_SEED=false \
185+
UV_NO_CACHE=true \
186+
UV_SYSTEM_PYTHON=1
187+
188+
###############
189+
# Setup virtualenv
190+
###############
191+
# Using UV (Universal Virtualenv) to create a virtual environment
192+
# UV is used in place of poetry for speed and simplicity.
193+
# NOTE: this is separate from the jupyter user
194+
ENV JUPYTER_HOME=/usr/jupytervenv
195+
ENV VIRTUAL_ENV=$JUPYTER_HOME
196+
197+
COPY uv.lock .
198+
COPY pyproject.toml .
199+
200+
# Add jupyter virtual environment to PATH,
201+
# but make sure to add it at the end so that the
202+
# Conda base python takes precedence
203+
# (aka the ! operator in iPython shells should NOT access the jupyter virtualenvironment)
204+
ENV PATH "${PATH}:${JUPYTER_HOME}/bin"
205+
ENV PATH="/root/.local/bin/:$PATH"
206+
207+
# Download the latest installer
208+
ADD https://astral.sh/uv/install.sh /uv-installer.sh
209+
#--python $BASE_PYTHON_PATH
210+
RUN sh /uv-installer.sh && rm /uv-installer.sh \
211+
# Create a virtual environment and activate it for UV to use
212+
&& uv venv $JUPYTER_HOME --python $BASE_PYTHON_PATH \
213+
&& source $JUPYTER_HOME/bin/activate \
214+
&& PYTHONEXECUTABLE=/usr/bin/env python3 \
215+
# Install the python dependencies using uv
216+
&& uv pip install wheel \
217+
&& uv pip install 'setuptools==59.8.0' \
218+
&& uv pip install -r pyproject.toml --no-cache --no-build-isolation \
219+
# Cleanup
220+
&& rm uv.lock && rm pyproject.toml
221+
# Uninstall uv
222+
# && uv cache clean \
223+
# && rm ~/.local/bin/uv ~/.local/bin/uvx
224+
225+
# Install nbstripout
226+
RUN nbstripout --install --global
227+
228+
# ##################################
229+
# # Terra-specific Jupyter Utilities
230+
# ##################################
231+
# Ensure this matches c.NotebookApp.port in 'jupyter_notebook_config.py'
232+
ENV JUPYTER_PORT=8000
233+
EXPOSE $JUPYTER_PORT
234+
ENV JUPYTER_KERNELSPEC_DIR=/usr/local/share/jupyter
235+
236+
# Install nbstripout
237+
RUN nbstripout --install --global
238+
239+
# copy workspace_cromwell.py script and make it runnable by all users
240+
RUN curl -o /usr/local/bin/workspace_cromwell.py https://raw.githubusercontent.com/broadinstitute/cromwhelm/1ceedf89587cffd355f37401b179001f029f77ed/scripts/workspace_cromwell.py \
241+
&& chmod +x /usr/local/bin/workspace_cromwell.py
242+
243+
# Copy over custom extensions
244+
COPY scripts $JUPYTER_HOME/scripts
245+
COPY custom $JUPYTER_HOME/custom
246+
COPY jupyter_notebook_config.py $JUPYTER_HOME
247+
RUN chown -R $JUPYTER_USER:users $JUPYTER_HOME
248+
249+
# Remove the jupyter environment from the list of available kernels so it is hidden from the user
250+
# NOTE: This depends on setting the c.KernelSpecManager.ensure_native_kernel flag
251+
# to False in 'jupyter_server_config.py'
252+
#RUN $JUPYTER_HOME/bin/jupyter kernelspec remove python3 -y
253+
254+
# setup the jupyter kernel
255+
RUN chown -R $JUPYTER_USER:users $JUPYTER_KERNELSPEC_DIR \
256+
&& find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \
257+
# You can get kernel directory by running `jupyter kernelspec list`
258+
&& $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel $JUPYTER_KERNELSPEC_DIR/kernels
259+
260+
# Set up the user and working directory, which is where the persistent disk will be mounted
261+
# this is different from where Jupyter is installed
262+
USER $JUPYTER_USER
263+
WORKDIR $JUPYTER_USER_HOME
264+
265+
# Note: this entrypoint is provided for running Jupyter independently of Leonardo.
266+
# When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable
267+
# additional setup inside the container before execution. Jupyter execution occurs when the
268+
# init-actions.sh script uses 'docker exec' to call run-jupyter.sh.
269+
# .venv/bin/jupyter lab
270+
#ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "lab"]

terra-base/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# terra-jupyter-base image
2+
3+
This repo contains the terra-jupyter-base image that is compatible with notebook service in [Terra]("https://app.terra.bio/") called Leonardo. For example, use `us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:{version}` in terra.
4+
5+
## Image contents
6+
7+
The terra-jupyter-base extends the [Ubuntu base image]() TODO by including the following:
8+
9+
- OS prerequisites
10+
- google-cloud-sdk
11+
- Python 3.10
12+
- conda
13+
- Jupyter & JupyterLab
14+
- Leonardo customizations/extensions
15+
- Terra notebook utils
16+
17+
To see the complete contents of this image please see the [Dockerfile](./Dockerfile).
18+
19+
## Selecting prior versions of this image
20+
21+
To select an older version this image, you can search the [CHANGELOG.md](./CHANGELOG.md) for a specific package version you need.
22+
23+
Once you find an image version that you want, simply copy and paste the image url from the changelog into the corresponding custom docker field in the Terra notebook runtime widget.
24+
25+
## Updating the UV packages
26+
To update UV packages, first cd into the`terra-base` directory, then either:
27+
- modify the pyproject.toml file, then run `uv lock`
28+
- run `uv update` to update all packages in the project
29+
- run `uv add <package_name>` or remove to add or remove a specific package in the project
30+
31+
## Building the image
32+
To build the image locally, run the following command in the root of the repo:
33+
34+
```bash
35+
docker build terra-base -t us.gcr.io/broad-dsp-gcr-public/terra-base:{version}
36+
docker push us.gcr.io/broad-dsp-gcr-public/terra-base:{version}
37+
```
38+
39+
## NOTE: Changing paths
40+
If you change the following paths for the:
41+
- `JUPYTER_HOME`
42+
- `JUPYTER_USER`
43+
- `CONDA_HOME`
44+
45+
You may need to update the following files appropriately:
46+
- terra-docker
47+
- `conda_init.txt`
48+
- `run_jupyter.sh`
49+
- the notebook extension scripts
50+
- leonardo
51+
- `gce-init.sh`
52+
- the `jupyterUserhome` in RuntimeTemplateValues

terra-base/conda-env.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: terra-base
2+
channels:
3+
- defaults
4+
dependencies:
5+
- pip=23.1.2
6+
- python=3.10.12
7+
- pip:
8+
- bgzip==0.3.5
9+
- firecloud>=0.16.38
10+
- ipykernel==6.29.4
11+
- terra-notebook-utils>=0.15.0
12+
- cromshell>=2.1.1

terra-base/conda_init.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# >>> conda initialize >>>
2+
# !! Contents within this block are managed by 'conda init' !!
3+
__conda_setup="$('/home/jupyter/.envs/base-python3.10/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
4+
if [ $? -eq 0 ]; then
5+
eval "$__conda_setup"
6+
else
7+
if [ -f "/home/jupyter/.envs/base-python3.10/etc/profile.d/conda.sh" ]; then
8+
. "/home/jupyter/.envs/base-python3.10/etc/profile.d/conda.sh"
9+
else
10+
export PATH="/home/jupyter/.envs/base-python3.10/bin:$PATH"
11+
fi
12+
fi
13+
unset __conda_setup
14+
# <<< conda initialize <<<

terra-base/custom/.eslintrc.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module.exports = {
2+
'env': {
3+
'browser': true,
4+
'es6': true,
5+
},
6+
'extends': [
7+
'google',
8+
],
9+
'globals': {
10+
'Atomics': 'readonly',
11+
'SharedArrayBuffer': 'readonly',
12+
},
13+
'parserOptions': {
14+
'ecmaVersion': 2018,
15+
'sourceType': 'module',
16+
},
17+
'rules': {
18+
'comma-dangle': ['error', 'never'],
19+
//80 is the standard, but this required the least refactoring.
20+
'max-len': ['error', { 'code': 150 }],
21+
//TODO: remove the following rules and fix the offenses
22+
'require-jsdoc': ['error', {
23+
'require': {
24+
'FunctionDeclaration': false,
25+
'MethodDefinition': false,
26+
'ClassDeclaration': false,
27+
'ArrowFunctionExpression': false,
28+
'FunctionExpression': false
29+
}
30+
}]
31+
},
32+
};

0 commit comments

Comments
 (0)