diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000000..cf300669c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,56 @@ +name: "Documentation ๐Ÿ“–" +description: Did you find an error in our documentation? Report your findings here. +title: "[DOC] - " +labels: ["area: documentation ๐Ÿ“–"] + +body: + - type: markdown + attributes: + value: | + # Welcome ๐Ÿ‘‹ + + Thanks for using Nebari and taking some time to contribute to this project. + + Please fill out each section below. This info allows Nebari maintainers to diagnose (and fix!) your issue as + quickly as possible. + Before submitting a bug, please make sure the issue hasn't been already addressed by searching through + [the past issues](https://github.com/nebari-dev/nebari-docs/issues). + + Useful links: + + - Documentation: https://www.nebari.dev + - Contribution guidelines: https://www.nebari.dev/community/ + + - type: checkboxes + attributes: + label: Preliminary Checks + description: Please make sure that you verify each checkbox and follow the instructions for them. + options: + - label: "This issue is not a question, feature request, RFC, or anything other than a bug report. Please post those things in GitHub Discussions: https://github.com/nebari-dev/nebari/discussions" + required: true + - type: textarea + validations: + required: true + attributes: + label: Summary + description: | + What problem(s) did you run into that caused you to request a fix to the documentation or additional + documentation? What questions do you think we should answer? + + - type: textarea + validations: + required: true + attributes: + label: Steps to Resolve this Issue + description: | + How can the problem be solved? Are there any additional steps required? Do any other pages need to be updated? + value: | + 1. + 2. + 3. + ... + + - type: markdown + attributes: + value: > + Thanks for contributing ๐ŸŽ‰! diff --git a/.github/workflows/build_push_docker.yaml b/.github/workflows/build_push_docker.yaml new file mode 100644 index 0000000000..af4ff271e9 --- /dev/null +++ b/.github/workflows/build_push_docker.yaml @@ -0,0 +1,123 @@ +# Build and push images to: +# GitHub Container Registry (ghcr.io) +# Red Hat Container Registry (quay.io) +name: "Build Docker Images" + +on: + workflow_dispatch: null + push: + branches: + - "*" + paths: + - "Dockerfile" + - "dask-worker/*" + - "jupyterhub/*" + - "jupyterlab/*" + - "nebari-workflow-controller/*" + + - "scripts/*" + + - ".github/workflows/build-push-docker.yaml" + tags: + - "*" + +env: + DOCKER_ORG: nebari + GPU_BASE_IMAGE: nvidia/cuda:12.8.1-base-ubuntu24.04 + GPU_IMAGE_SUFFIX: gpu + BASE_IMAGE: ubuntu:24.04 + +permissions: + contents: read + packages: write + id-token: write + security-events: write + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # only cancel in-progress jobs or runs for the current workflow - matches against branch & tags + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-images: + name: "Build Docker Images" + runs-on: ubuntu-latest + strategy: + matrix: + dockerfile: + - jupyterlab + - jupyterhub + - dask-worker + - workflow-controller + platform: + - gpu + - cpu + exclude: + # excludes JupyterHub/GPU, Workflow Controller/GPU + - dockerfile: jupyterhub + platform: gpu + - dockerfile: workflow-controller + platform: gpu + + steps: + - name: "Checkout Repository ๐Ÿ›Ž๏ธ" + uses: actions/checkout@v3 + + - name: "Set up Docker Buildx ๐Ÿ› ๏ธ" + uses: docker/setup-buildx-action@v2 + + - name: "Login to GitHub Container Registry ๐Ÿ”" + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.BOT_GHCR_TOKEN }} + + - name: "Login to Quay Container Registry ๐Ÿ”" + uses: docker/login-action@v2 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_TOKEN }} + + - name: "Set BASE_IMAGE and Image Suffix ๐Ÿ“ท" + if: ${{ matrix.platform == 'gpu' }} + run: | + echo "GPU Platform Matrix" + echo "BASE_IMAGE=$GPU_BASE_IMAGE" >> $GITHUB_ENV + echo "IMAGE_SUFFIX=-$GPU_IMAGE_SUFFIX" >> $GITHUB_ENV + + - name: "Generate Docker images tags ๐Ÿท๏ธ" + id: meta + uses: docker/metadata-action@v4 + with: + images: | + "quay.io/${{ env.DOCKER_ORG }}/nebari-${{ matrix.dockerfile }}${{ env.IMAGE_SUFFIX }}" + "ghcr.io/${{ github.repository_owner }}/nebari-${{ matrix.dockerfile }}${{ env.IMAGE_SUFFIX }}" + tags: | + # branch event -> e.g. `main-f0f6994-20221001` + type=ref, event=branch, suffix=-{{sha}}-{{date 'YYYYMMDD'}} + # needed for integration tests + type=ref, event=branch + # on tag push -> e.g. `2022.10.1` + type=ref, event=tag + + - name: "Inspect image dir tree ๐Ÿ”" + run: | + sudo apt-get install tree + tree . + + - name: "Build docker images ๐Ÿณ" + uses: docker/build-push-action@v3 + with: + context: . + file: "Dockerfile" + target: ${{ matrix.dockerfile }} + tags: ${{ steps.meta.outputs.tags }} + push: ${{ github.event_name != 'pull_request' }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: BASE_IMAGE=${{ env.BASE_IMAGE }} + platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/docker_trivy.yaml b/.github/workflows/docker_trivy.yaml new file mode 100644 index 0000000000..5644a3d5e9 --- /dev/null +++ b/.github/workflows/docker_trivy.yaml @@ -0,0 +1,40 @@ +name: Code Scanning + +on: + push: + branches: [ "main"] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + +permissions: + contents: read + +jobs: + SAST: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Trivy config Scan + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner in config mode + uses: aquasecurity/trivy-action@master + with: + scan-type: 'config' + hide-progress: true + format: 'sarif' + output: 'trivy-results.sarif' + ignore-unfixed: true + severity: 'CRITICAL,HIGH' + limit-severities-for-sarif: true + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/test_images.yaml b/.github/workflows/test_images.yaml new file mode 100644 index 0000000000..d3a6a19620 --- /dev/null +++ b/.github/workflows/test_images.yaml @@ -0,0 +1,77 @@ +name: Test Docker images + +on: + pull_request: + paths: + - "Dockerfile.*" + + - "dask-worker/*" + - "jupyterhub/*" + - "jupyterlab/*" + + - "scripts/*" + + - ".github/workflows/build-push-docker.yaml" + - ".github/workflows/test-images.yaml" + +env: + DOCKER_ORG: nebari + GITHUB_SHA: ${{ github.sha }} + GPU_BASE_IMAGE: nvidia/cuda:12.8.1-base-ubuntu24.04 + GPU_IMAGE_SUFFIX: gpu + BASE_IMAGE: ubuntu:24.04 + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # only cancel in-progress jobs or runs for the current workflow - matches against branch & tags + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-test-images: + runs-on: ubuntu-latest + strategy: + matrix: + dockerfile: + - jupyterlab + - jupyterhub + - dask-worker + platform: + - gpu + - cpu + exclude: + # excludes JupyterHub/GPU + - dockerfile: jupyterhub + platform: gpu + steps: + - name: Checkout Repository ๐Ÿ›Ž + uses: actions/checkout@v3 + + - name: Lint Dockerfiles ๐Ÿ” + uses: jbergstroem/hadolint-gh-action@v1 + with: + dockerfile: Dockerfile + output_format: tty + error_level: 0 + + - name: "Set BASE_IMAGE and Image Suffix ๐Ÿ“ท" + if: ${{ matrix.platform == 'gpu' }} + run: | + echo "GPU Platform Matrix" + echo "BASE_IMAGE=$GPU_BASE_IMAGE" >> $GITHUB_ENV + echo "IMAGE_SUFFIX=-$GPU_IMAGE_SUFFIX" >> $GITHUB_ENV + + - name: "Set up Docker Buildx ๐Ÿ› ๏ธ" + uses: docker/setup-buildx-action@v2 + + - name: Build Image ๐Ÿ›  + uses: docker/build-push-action@v3 + with: + context: . + file: "Dockerfile" + target: ${{ matrix.dockerfile }} + push: false + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: BASE_IMAGE=${{ env.BASE_IMAGE }} + platforms: linux/amd64,linux/arm64 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fd8676936..24b3bfaa36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -82,3 +82,28 @@ repos: - id: terraform_fmt args: - --args=-write=true + + # Autoformat: markdown, yaml to ensure that it doesn't need to be updated in other repos + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.6.1 + hooks: + - id: prettier + + # Misc... + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + # ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available + hooks: + # Autoformat: Makes sure files end in a newline and only a newline. + - id: end-of-file-fixer + + # Trims trailing whitespace. + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + # Lint: Check for files with names that would conflict on a + # case-insensitive filesystem like MacOS HFS+ or Windows FAT. + - id: check-case-conflict + + # Lint: Checks that non-binary executables have a proper shebang. + - id: check-executables-have-shebangs diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..b634b69fcb --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,158 @@ +FROM ubuntu:24.04 AS builder +LABEL MAINTAINER="Nebari development team" + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + wget \ + bzip2 \ + ca-certificates \ + curl \ + git + +COPY scripts /opt/scripts + +ENV MAMBAFORGE_VERSION=4.13.0-1 \ + MAMBAFORGE_AARCH64_SHA256=69e3c90092f61916da7add745474e15317ed0dc6d48bfe4e4c90f359ba141d23 \ + MAMBAFORGE_X86_64_SHA256=412b79330e90e49cf7e39a7b6f4752970fcdb8eb54b1a45cc91afe6777e8518c \ + PATH=/opt/conda/bin:${PATH}:/opt/scripts + + +RUN /opt/scripts/install-conda.sh + + + +# ========== dask-worker install =========== +FROM builder AS dask-worker +COPY dask-worker/environment.yaml /opt/dask-worker/environment.yaml +RUN --mount=type=cache,target=/opt/conda/pkgs,sharing=locked \ + --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + /opt/scripts/install-conda-environment.sh /opt/dask-worker/environment.yaml 'false' + +ENV LD_LIBRARY_PATH=/usr/local/nvidia/lib64 +ENV NVIDIA_PATH=/usr/local/nvidia/bin +ENV PATH="$NVIDIA_PATH:$PATH" + +COPY dask-worker /opt/dask-worker +RUN /opt/dask-worker/postBuild + + + + + +# ========== jupyterhub install =========== +FROM builder AS jupyterhub +COPY jupyterhub/environment.yaml /opt/jupyterhub/environment.yaml +RUN --mount=type=cache,target=/opt/conda/pkgs,sharing=locked \ + --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + /opt/scripts/install-conda-environment.sh /opt/jupyterhub/environment.yaml 'false' + +COPY jupyterhub /opt/jupyterhub +RUN /opt/jupyterhub/postBuild + +WORKDIR /srv/jupyterhub + +# So we can actually write a db file here +RUN fix-permissions /srv/jupyterhub + +CMD ["jupyterhub", "--config", "/usr/local/etc/jupyterhub/jupyterhub_config.py"] + + + + +# ========== jupyterlab base =========== +FROM builder AS intermediate +ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 \ + CONDA_DIR=/opt/conda \ + DEFAULT_ENV=default +RUN chmod -R a-w ~ +ENV TZ=UTC \ + PATH=/opt/conda/envs/${DEFAULT_ENV}/bin:/opt/conda/bin:${PATH}:/opt/scripts +# Set timezone +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + locales \ + libnss-wrapper \ + htop \ + tree \ + zip \ + unzip \ + openssh-client \ + tmux \ + xvfb \ + nano \ + vim \ + emacs + + +# ========== jupyterlab install =========== +FROM intermediate AS jupyterlab +ENV CONDA_DIR=/opt/conda \ + DEFAULT_ENV=default \ + LD_LIBRARY_PATH=/usr/local/nvidia/lib64 \ + NVIDIA_PATH=/usr/local/nvidia/bin + +ENV PATH="$NVIDIA_PATH:$PATH" + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + zsh \ + neovim \ + libgl1 \ + libglx-mesa0 \ + libxrandr2 \ + libxss1 \ + libxcursor1 \ + libxcomposite1 \ + libasound2t64 \ + libxi6 \ + libxtst6 \ + libfontconfig1 \ + libxrender1 \ + libosmesa6 \ + gnupg \ + pinentry-curses \ + git-lfs + +ARG SKIP_CONDA_SOLVE=no +COPY jupyterlab/environment.yaml /opt/jupyterlab/environment.yaml +RUN --mount=type=cache,target=/opt/conda/pkgs,sharing=locked \ + --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + if [ "${SKIP_CONDA_SOLVE}" != "no" ];then \ + ENV_FILE=/opt/jupyterlab/conda-linux-64.lock ; \ + else \ + ENV_FILE=/opt/jupyterlab/environment.yaml ; \ + fi ; \ + /opt/scripts/install-conda-environment.sh "${ENV_FILE}" 'true' + +# ========== code-server install ============ +ENV PATH=/opt/conda/envs/${DEFAULT_ENV}/share/code-server/bin:${PATH} + +COPY jupyterlab /opt/jupyterlab +RUN /opt/jupyterlab/postBuild + + + + + +# ========== nebari-workflow-controller install ============ +FROM intermediate AS workflow-controller + +ARG SKIP_CONDA_SOLVE=no +COPY nebari-workflow-controller/environment.yaml /opt/nebari-workflow-controller/environment.yaml +RUN --mount=type=cache,target=/opt/conda/pkgs,sharing=locked \ + --mount=type=cache,target=/root/.cache/pip,sharing=locked \ + if [ "${SKIP_CONDA_SOLVE}" != "no" ];then \ + ENV_FILE=/opt/nebari-workflow-controller/conda-linux-64.lock ; \ + else \ + ENV_FILE=/opt/nebari-workflow-controller/environment.yaml ; \ + fi ; \ + /opt/scripts/install-conda-environment.sh "${ENV_FILE}" 'true' + +COPY nebari-workflow-controller /opt/nebari-workflow-controller + +CMD ["python", "-m", "nebari_workflow_controller"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..5a0553c885 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,135 @@ +<p align="center"> +<picture> + <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/nebari-dev/nebari-design/main/logo-mark/horizontal/Nebari-Logo-Horizontal-Lockup.svg"> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nebari-dev/nebari-design/main/logo-mark/horizontal/Nebari-Logo-Horizontal-Lockup-White-text.svg"> + <img alt="Nebari logo mark - text will be black in light color mode and white in dark color mode." src="https://raw.githubusercontent.com/nebari-dev/nebari-design/main/logo-mark/horizontal/Nebari-Logo-Horizontal-Lockup-White-text.svg" width="50%"/> +</picture> +</p> + +--- + +# Nebari base Docker images + +| Information | Links | +| :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Project | [![License - BSD3 License badge](https://img.shields.io/badge/License-BSD%203--Clause-gray.svg?colorA=2D2A56&colorB=5936D9&style=flat.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Nebari documentation badge - nebari.dev](https://img.shields.io/badge/%F0%9F%93%96%20Read-the%20docs-gray.svg?colorA=2D2A56&colorB=5936D9&style=flat.svg)][nebari-docs] | +| Community | [![GH discussions badge](https://img.shields.io/badge/%F0%9F%92%AC%20-Participate%20in%20discussions-gray.svg?colorA=2D2A56&colorB=5936D9&style=flat.svg)][nebari-discussions] [![Open a GH issue badge](https://img.shields.io/badge/%F0%9F%93%9D%20Open-an%20issue-gray.svg?colorA=2D2A56&colorB=5936D9&style=flat.svg)][nebari-docker-issues] | +| CI | ![Build Docker Images - GitHub action status badge](https://github.com/nebari-dev/nebari-docker-images/actions/workflows/build-push-docker.yaml/badge.svg) | + +- [Nebari base Docker images](#nebari-base-docker-images) + - [Getting started โšก๏ธ](#getting-started-๏ธ) + - [Prerequisites ๐Ÿ’ป](#prerequisites-) + - [Building the Docker images ๐Ÿ› ](#building-the-docker-images-) + - [Pre-commit hooks ๐Ÿงน](#pre-commit-hooks-) + - [Reporting an issue ๐Ÿ“](#reporting-an-issue-) + - [Contributions ๐Ÿค](#contributions-) + - [License ๐Ÿ“„](#license-) + +This repository contains the source code for Docker (container) images used by the [Nebari platform][nebari-docs]. It also contains an automated means of building and pushing these images to public container registries through [GitHub actions][nebari-docker-actions]. Currently, these images are built and pushed to the following registries: + +**GitHub Container Registry (ghcr.io)** + +- [`nebari-jupyterlab`](https://github.com/orgs/nebari-dev/packages/container/package/nebari-jupyterlab) +- [`nebari-jupyterlab-gpu`](https://github.com/orgs/nebari-dev/packages/container/package/nebari-jupyterlab-gpu) +- [`nebari-jupyterhub`](https://github.com/orgs/nebari-dev/packages/container/package/nebari-jupyterhub) +- [`nebari-dask-worker`](https://github.com/orgs/nebari-dev/packages/container/package/nebari-dask-worker) +- [`nebari-dask-worker-gpu`](https://github.com/orgs/nebari-dev/packages/container/package/nebari-dask-worker-gpu) + +**Quay Container Registry (quay.io)** + +- [`nebari-jupyterlab`](https://quay.io/repository/nebari/nebari-jupyterlab) +- [`nebari-jupyterlab-gpu`](https://quay.io/repository/nebari/nebari-jupyterlab-gpu) +- [`nebari-jupyterhub`](https://quay.io/repository/nebari/nebari-jupyterhub) +- [`nebari-dask-worker`](https://quay.io/repository/nebari/nebari-dask-worker) +- [`nebari-dask-worker-gpu`](https://quay.io/repository/nebari/nebari-dask-worker-gpu) + +## Getting started โšก๏ธ + +Whether you want to contribute to this project or whether you wish use these images, to get started, fork this repo and then clone the forked repo onto your local machine. + +### Prerequisites ๐Ÿ’ป + +- [`docker`](https://docs.docker.com/get-docker/), make sure to read the [Docker official documentation on how to install Docker on your machine](https://docs.docker.com/get-docker/). +- [pre-commit](https://pre-commit.com/), which can be installed with: + + ```bash + pip install pre-commit + # or using conda + conda install -c conda-forge pre-commit + ``` + +### Building the Docker images ๐Ÿ›  + +From the repository's root folder, you can build these images locally by running the listed commands on your terminal. + +- To build nebari-jupyterlab + + ```shell + make jupyterlab + ``` + +- To build nebari-jupyterhub + + ```shell + make jupyterhub + ``` + +- To build nebari-dask-worker + + ```shell + make dask-worker + ``` + +- To build nebari-workflow-controller + + ```shell + make workflow-controller + ``` + +- To build all of the images + + ```shell + make all + ``` +- To delete built images + + ```shell + make clean + ``` + +> **NOTE** +> It is extremely important to pin specific packages `dask-gateway` and `distributed` as they need to run the same version for the `dask-workers` to work as expected. + +### Pre-commit hooks ๐Ÿงน + +This repository uses the `prettier` pre-commit hook to standardize our YAML and markdown structure. +To install and run it, use these commands from the repository root: + +```bash +# install the pre-commit hooks +pre-commit install + +# run the pre-commit hooks +pre-commit run --all-files +``` + +## Reporting an issue ๐Ÿ“ + +If you encounter an issue or want to make suggestions on how we can make this project better, feel free to [open an issue on this repository's issue tracker](https://github.com/nebari-dev/nebari-docker-images/issues/new/choose). + +## Contributions ๐Ÿค + +Thinking about contributing to this repository or any other in the Nebari org? Check out our +[Contribution Guidelines](https://nebari.dev/community). + +## License ๐Ÿ“„ + +[Nebari is BSD3 licensed](LICENSE). + +<!-- Links --> + +[nebari-docker-repo]: https://github.com/nebari-dev/nebari-docker-images +[nebari-docker-issues]: https://github.com/nebari-dev/nebari-docker-images/issues/new/choose +[nebari-docker-actions]: https://github.com/nebari-dev/nebari-docker-images/actions +[nebari-discussions]: https://github.com/orgs/nebari-dev/discussions +[nebari-docs]: https://nebari.dev diff --git a/docker/dask-worker/environment.yaml b/docker/dask-worker/environment.yaml new file mode 100644 index 0000000000..b41fc82d33 --- /dev/null +++ b/docker/dask-worker/environment.yaml @@ -0,0 +1,9 @@ +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +name: base +channels: + - conda-forge +dependencies: + # dask + - nebari-dask diff --git a/docker/dask-worker/postBuild b/docker/dask-worker/postBuild new file mode 100644 index 0000000000..72cee969b8 --- /dev/null +++ b/docker/dask-worker/postBuild @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -euo pipefail + +# A workaround that allows a command to run in a +# specific conda environment +cat <<EOF >/opt/conda-run-worker +#!/bin/bash +set -xe + +source activate \$CONDA_ENVIRONMENT +dask-worker "\$@" +EOF + +cat <<EOF >/opt/conda-run-scheduler +#!/bin/bash +set -xe + +source activate \$CONDA_ENVIRONMENT +dask-scheduler "\$@" +EOF + +chmod 755 /opt/conda-run-worker +chmod 755 /opt/conda-run-scheduler diff --git a/docker/jupyterhub/environment.yaml b/docker/jupyterhub/environment.yaml new file mode 100644 index 0000000000..d313d3e3a5 --- /dev/null +++ b/docker/jupyterhub/environment.yaml @@ -0,0 +1,17 @@ +name: base +channels: + - conda-forge +dependencies: + - pip==21.1.2 + - jupyterhub==5.3.0 + - jupyterhub-kubespawner==6.2.0 + - oauthenticator==16.3.0 + - escapism==1.0.1 + - python-kubernetes + - kubernetes_asyncio==29.0.0 + - jupyterhub-idle-culler==1.2.1 + - sqlalchemy==1.4.46 + - pip: + - nebari-jupyterhub-theme==2024.7.1 + - python-keycloak==0.26.1 + - jhub-apps==2025.2.1 diff --git a/docker/jupyterhub/postBuild b/docker/jupyterhub/postBuild new file mode 100644 index 0000000000..6b7e4da137 --- /dev/null +++ b/docker/jupyterhub/postBuild @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -euo pipefail diff --git a/docker/jupyterlab/environment.yaml b/docker/jupyterlab/environment.yaml new file mode 100644 index 0000000000..ceac12c947 --- /dev/null +++ b/docker/jupyterlab/environment.yaml @@ -0,0 +1,69 @@ +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +name: default +channels: + - conda-forge +dependencies: + # general + - pip + + # jupyterhub/jupyterlab + - nb_conda_kernels + - ipython > 7 + - jupyter-server-proxy >=4.4.0 + - "jupyter_server>=2.13.0" + - jupyterlab==4.4.2 + - jupyter_client + - jupyter_console + - jupyterhub==5.3.0 + - nbconvert + - nbval + + # jupyterhub extension + + # jupyterlab extensions + - dask_labextension >= 5.3.0 + - jupyterlab-git >=0.30.0 + - sidecar >=0.5.0 + - ipywidgets >= 8.0.0 + - ipyleaflet >=0.13.5 + - pyviz_comms >=3.0.3 + - jupyter-resource-usage >=0.6.0 + - nbgitpuller + - jupyterlab_code_formatter + - jupyterlab-spellchecker >= 0.7.3 + - jupyterlab-pioneer + - jupyter-ai + - jupyterlab-favorites >=3.2.1 + - jupyter-scheduler >=2.8.0,<3.0.0 # >=2.8 due to https://github.com/conda-forge/jupyter_scheduler-feedstock/issues/46 + + # viz tools + - param + - python-graphviz + - plotly >=5.0 + - ipympl >=0.9.6 + - bokeh >=3.5.2 + + # testing, docs, linting + - pytest + - hypothesis + - flake8 + - sphinx + - pytest-cov + - black + - isort + - importnb + - git-lfs + + - pip: + # vscode jupyterlab launcher + - git+https://github.com/betatim/vscode-binder + - jupyterlab_nvdashboard==0.12.0 + - argo-jupyter-scheduler==2024.6.1 + - jhub-apps==2025.2.1 + - jupyterlab-nebari-mode==0.3.0 + - jupyterlab-conda-store==2024.11.1 + - jupyterlab-launchpad==1.0.3 + - jupyterlab-gallery==0.6.3 + - jupyterlab-jhub-apps==0.3.1 diff --git a/docker/jupyterlab/postBuild b/docker/jupyterlab/postBuild new file mode 100644 index 0000000000..67fb479153 --- /dev/null +++ b/docker/jupyterlab/postBuild @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +set -x + +# install code-server extension +sh /opt/scripts/install-code-server.sh "/opt/conda/envs/${DEFAULT_ENV}/share" + +# if DEFAULT_ENV is unset ${DEFAULT_ENV+x} expands to nothing otherwise +# it substitutes the string x. This allows us to check if the variable +# is set without triggering an unbound variable error +if [[ -z "${DEFAULT_ENV+x}" ]]; then + fix-permissions /opt/conda/bin +else + fix-permissions "/opt/conda/envs/${DEFAULT_ENV}" +fi diff --git a/docker/makefile b/docker/makefile new file mode 100644 index 0000000000..c22d0558d2 --- /dev/null +++ b/docker/makefile @@ -0,0 +1,27 @@ +IMAGES := jupyterhub jupyterlab dask-worker workflow-controller +DOCKERFILE := Dockerfile +CONTEXT := . + +.PHONY: all $(IMAGES) clean + +# Build all images +all: $(IMAGES) + +# Build individual images +jupyterhub: + docker build -t nebari-dev/nebari-docker-images:nebari-jupyterhub -f $(DOCKERFILE) $(CONTEXT) --target jupyterhub + +jupyterlab: + docker build -t nebari-dev/nebari-docker-images:nebari-jupyterlab -f $(DOCKERFILE) $(CONTEXT) --target jupyterlab + +dask-worker: + docker build -t nebari-dev/nebari-docker-images:nebari-dask-worker -f $(DOCKERFILE) $(CONTEXT) --target dask-worker + +workflow-controller: + docker build -t nebari-dev/nebari-docker-images:nebari-workflow-controller -f $(DOCKERFILE) $(CONTEXT) --target workflow-controller + +# Clean up images +clean: + @for image in $(IMAGES); do \ + docker rmi nebari-dev/nebari-docker-images:nebari-$$image; \ + done diff --git a/docker/nebari-workflow-controller/environment.yaml b/docker/nebari-workflow-controller/environment.yaml new file mode 100644 index 0000000000..62eef8bb36 --- /dev/null +++ b/docker/nebari-workflow-controller/environment.yaml @@ -0,0 +1,8 @@ +name: default +channels: + - conda-forge +dependencies: + - python=3.10 + - pip + - pip: + - nebari-workflow-controller==2023.7.1 diff --git a/docker/scripts/fix-permissions b/docker/scripts/fix-permissions new file mode 100644 index 0000000000..8e9926c02c --- /dev/null +++ b/docker/scripts/fix-permissions @@ -0,0 +1,15 @@ +#!/bin/bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +# uses find to avoid touching files that already have the right permissions +# right permissions are: +# world rX, we have no guarantees of uids or gids upon +# deployment so we want files accessible to all. + +set -e +for d in "$@"; do + find "$d" \ + ! -perm -o+rX \ + -exec chmod o+rX {} \; +done diff --git a/docker/scripts/install-code-server.sh b/docker/scripts/install-code-server.sh new file mode 100644 index 0000000000..9c5700c65e --- /dev/null +++ b/docker/scripts/install-code-server.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -xe +DEFAULT_PREFIX="${1}" +shift # path to environment yaml or lock file +CODE_SERVER_VERSION=4.23.1 + +mkdir -p ${DEFAULT_PREFIX}/code-server +cd ${DEFAULT_PREFIX}/code-server + +# Fetch the snapshot of https://code-server.dev/install.sh as of the time of writing +wget --quiet https://raw.githubusercontent.com/coder/code-server/v4.23.1/install.sh +expected_sum=ef0324043bc7493989764315e22bbc85c38c4e895549538b7e701948b64495e6 + +if [[ ! $(sha256sum install.sh) == "${expected_sum} install.sh" ]]; then + echo Unexpected hash from code-server install script + exit 1 +fi + +mkdir /opt/tmpdir +sh ./install.sh --method standalone --prefix /opt/tmpdir --version ${CODE_SERVER_VERSION} + +mv /opt/tmpdir/lib/code-server-${CODE_SERVER_VERSION}/* ${DEFAULT_PREFIX}/code-server +rm -rf /opt/tmpdir diff --git a/docker/scripts/install-conda-environment.sh b/docker/scripts/install-conda-environment.sh new file mode 100644 index 0000000000..5ad070dfcc --- /dev/null +++ b/docker/scripts/install-conda-environment.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -xe +ENV_FILE="${1}" +shift # path to environment yaml or lock file +NEW_ENV="${1}" +shift # true or false indicating whether env update should occur + +# Capture last optional arg or set a ENV_NAME. This can be changed but be +# careful... setting the path for both the dockerfile and runtime container +# can be tricky +if [[ -z "${1+x}" ]] || [[ "${1}" == "" ]]; then + ENV_NAME=default +else + ENV_NAME="${1}" + shift +fi + +# Set a default value for skipping the conda solve (using a lock file). +: ${SKIP_CONDA_SOLVE:=no} + +# ==== install conda dependencies ==== + +if ! ${NEW_ENV}; then + if [[ $(basename "${ENV_FILE}") =~ "*lock*" ]]; then + echo "${ENV_FILE} should not be a lock file as this is not supported when \ + only updating the conda environment. Consider setting NEW_ENV to yes." + exit 1 + fi + echo Installing into current conda environment + mamba env update -f "${ENV_FILE}" + +# Env not being updated... create one now: +elif [[ "${SKIP_CONDA_SOLVE}" == "no" ]]; then + mamba env create --prefix=/opt/conda/envs/${ENV_NAME} -f "${ENV_FILE}" +elif [[ "${SKIP_CONDA_SOLVE}" == "yes" ]]; then + mamba create --prefix=/opt/conda/envs/${ENV_NAME} --file "${ENV_FILE}" + + # This needs to be set using the ENV directive in the docker file + PATH="/opt/conda/envs/${ENV_NAME}/bin:${PATH}" + # For now install pip section manually. We could consider using pip-tools... + # See https://github.com/conda-incubator/conda-lock/issues/4 + pip install https://github.com/dirkcgrunwald/jupyter_codeserver_proxy-/archive/5596bc9c2fbd566180545fa242c659663755a427.tar.gz +else + echo "SKIP_CONDA_SOLVE should be yes or no instead got: '${SKIP_CONDA_SOLVE}'" + exit 1 +fi + +# ========= list dependencies ======== +/opt/conda/bin/conda list + +# ========== cleanup conda =========== +/opt/conda/bin/mamba clean -afy +# remove unnecissary files (status, js.maps) +find /opt/conda/ -follow -type f -name '*.a' -delete +find /opt/conda/ -follow -type f -name '*.js.map' -delete + +# Fix permissions +fix-permissions "/opt/conda/envs/${ENV_NAME}" || fix-permissions /opt/conda/bin diff --git a/docker/scripts/install-conda.sh b/docker/scripts/install-conda.sh new file mode 100644 index 0000000000..feda68b221 --- /dev/null +++ b/docker/scripts/install-conda.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -xe + +# Requires environment MAMBAFORGE_SHA256, MINIFORGE_VERSION, and DEFAULT_ENV +arch=$(uname -i) +wget --quiet -O mambaforge.sh https://github.com/conda-forge/miniforge/releases/download/$MAMBAFORGE_VERSION/Mambaforge-Linux-$arch.sh + +if [[ $arch == "aarch64" ]]; then + echo "${MAMBAFORGE_AARCH64_SHA256} mambaforge.sh" >mambaforge.checksum +elif [[ $arch == "x86_64" ]]; then + echo "${MAMBAFORGE_X86_64_SHA256} mambaforge.sh" >mambaforge.checksum +else + echo "Unsupported architecture: $arch" + exit 1 +fi + +echo $(sha256sum -c mambaforge.checksum) + +if [ $(sha256sum -c mambaforge.checksum | awk '{print $2}') != "OK" ]; then + echo Error when testing checksum + exit 1 +fi + +# Install Mamba and clean-up +if [ -d "/opt/conda" ]; then + sh ./mambaforge.sh -b -u -p /opt/conda +else + sh ./mambaforge.sh -b -p /opt/conda +fi + +rm mambaforge.sh mambaforge.checksum + +mamba --version +mamba clean -afy + +ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh + +mkdir -p /etc/conda +cat <<EOF >/etc/conda/condarc +always_yes: true +changeps1: false +auto_update_conda: false +aggressive_update_packages: [] +envs_dirs: + - /home/conda/environments +EOF + +# Fix permissions in accordance with jupyter stack permissions +# model +fix-permissions /opt/conda /etc/conda /etc/profile.d diff --git a/docker/scripts/install-gitlfs.sh b/docker/scripts/install-gitlfs.sh new file mode 100644 index 0000000000..47dc9177f0 --- /dev/null +++ b/docker/scripts/install-gitlfs.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Copyright (c) Nebari Development Team. +# Distributed under the terms of the Modified BSD License. + +set -xe + +# Adding the packagecloud repository for git-lfs installation +wget --quiet -O script.deb.sh https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh +expected_sum=8c4d07257b8fb6d612b6085f68ad33c34567b00d0e4b29ed784b2a85380f727b + +if [[ ! $(sha256sum script.deb.sh) == "${expected_sum} script.deb.sh" ]]; then + echo Unexpected hash from git-lfs install script + exit 1 +fi + +# Install packagecloud's repository signing key and add repository to apt +bash ./script.deb.sh + +# Install git-lfs +apt-get install -y --no-install-recommends git-lfs