Full Code of jupyter/docker-stacks for AI

main 6da149350f01 cached
273 files
487.0 KB
134.0k tokens
196 symbols
1 requests
Download .txt
Showing preview only (548K chars total). Download the full file or copy to clipboard to get everything.
Repository: jupyter/docker-stacks
Branch: main
Commit: 6da149350f01
Files: 273
Total size: 487.0 KB

Directory structure:
gitextract_w6y_9n58/

├── .devcontainer/
│   ├── Dockerfile
│   └── devcontainer.json
├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── blank.yml
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actions/
│   │   ├── apply-single-tags/
│   │   │   └── action.yml
│   │   ├── create-dev-env/
│   │   │   └── action.yml
│   │   ├── free-disk-space/
│   │   │   └── action.yml
│   │   └── load-image/
│   │       └── action.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── contributed-recipes.yml
│       ├── docker-build-test-upload.yml
│       ├── docker-tag-merge.yml
│       ├── docker-tag-push-merge.yml
│       ├── docker-tag-push.yml
│       ├── docker-wiki-update.yml
│       ├── docker.yml
│       ├── pre-commit.yml
│       ├── registry-move.yml
│       ├── registry-overviews.yml
│       └── sphinx.yml
├── .gitignore
├── .hadolint.yaml
├── .markdownlint.yaml
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── SECURITY.md
├── binder/
│   ├── Dockerfile
│   └── README.ipynb
├── docs/
│   ├── conf.py
│   ├── contributing/
│   │   ├── features.md
│   │   ├── issues.md
│   │   ├── lint.md
│   │   ├── packages.md
│   │   ├── recipes.md
│   │   ├── stacks.md
│   │   └── tests.md
│   ├── index.rst
│   ├── maintaining/
│   │   ├── new-images-and-packages-policy.md
│   │   ├── tagging.md
│   │   ├── tagging_examples/
│   │   │   ├── docker_runner.py
│   │   │   └── git_helper.py
│   │   └── tasks.md
│   ├── requirements.txt
│   └── using/
│       ├── changelog.md
│       ├── common.md
│       ├── custom-images.md
│       ├── faq.md
│       ├── recipe_code/
│       │   ├── custom_environment.dockerfile
│       │   ├── dask_jupyterlab.dockerfile
│       │   ├── docker-bake.custom-python.hcl
│       │   ├── generate_matrix.py
│       │   ├── ijavascript.dockerfile
│       │   ├── jupyterhub_version.dockerfile
│       │   ├── mamba_install.dockerfile
│       │   ├── manpage_install.dockerfile
│       │   ├── microsoft_odbc.dockerfile
│       │   ├── oracledb.dockerfile
│       │   ├── pip_install.dockerfile
│       │   ├── requirements.txt
│       │   ├── rise_jupyterlab.dockerfile
│       │   ├── spellcheck_notebook_v6.dockerfile
│       │   └── xgboost.dockerfile
│       ├── recipes.md
│       ├── running.md
│       ├── selecting.md
│       ├── specifics.md
│       └── troubleshooting.md
├── examples/
│   ├── README.md
│   ├── docker-compose/
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── letsencrypt.sh
│   │   │   ├── sl-dns.sh
│   │   │   ├── softlayer.sh
│   │   │   └── vbox.sh
│   │   └── notebook/
│   │       ├── Dockerfile
│   │       ├── build.sh
│   │       ├── down.sh
│   │       ├── env.sh
│   │       ├── letsencrypt-notebook.yml
│   │       ├── notebook.yml
│   │       ├── secure-notebook.yml
│   │       └── up.sh
│   ├── make-deploy/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── letsencrypt.makefile
│   │   ├── self-signed.makefile
│   │   ├── softlayer.makefile
│   │   └── virtualbox.makefile
│   ├── openshift/
│   │   ├── README.md
│   │   └── templates.json
│   └── source-to-image/
│       ├── README.md
│       ├── assemble
│       ├── run
│       ├── save-artifacts
│       └── templates.json
├── images/
│   ├── all-spark-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── base-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── docker_healthcheck.py
│   │   ├── jupyter_server_config.py
│   │   ├── start-notebook.py
│   │   ├── start-notebook.sh
│   │   ├── start-singleuser.py
│   │   └── start-singleuser.sh
│   ├── datascience-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── docker-stacks-foundation/
│   │   ├── .dockerignore
│   │   ├── 10activate-conda-env.sh
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── fix-permissions
│   │   ├── initial-condarc
│   │   ├── run-hooks.sh
│   │   └── start.sh
│   ├── julia-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── minimal-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── Rprofile.site
│   │   └── setup-scripts/
│   │       ├── activate_notebook_custom_env.py
│   │       ├── setup-julia-packages.bash
│   │       └── setup_julia.py
│   ├── pyspark-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── ipython_kernel_config.py
│   │   └── setup_spark.py
│   ├── pytorch-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── cuda12/
│   │   │   └── Dockerfile
│   │   └── cuda13/
│   │       └── Dockerfile
│   ├── r-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── scipy-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   └── tensorflow-notebook/
│       ├── .dockerignore
│       ├── Dockerfile
│       ├── README.md
│       └── cuda/
│           ├── 20tensorboard-proxy-env.sh
│           ├── Dockerfile
│           └── nvidia-lib-dirs.sh
├── mypy.ini
├── requirements-dev.txt
├── tagging/
│   ├── README.md
│   ├── __init__.py
│   ├── apps/
│   │   ├── __init__.py
│   │   ├── apply_tags.py
│   │   ├── common_cli_arguments.py
│   │   ├── config.py
│   │   ├── merge_tags.py
│   │   ├── write_manifest.py
│   │   └── write_tags_file.py
│   ├── hierarchy/
│   │   ├── __init__.py
│   │   ├── get_manifests.py
│   │   ├── get_taggers.py
│   │   └── images_hierarchy.py
│   ├── manifests/
│   │   ├── __init__.py
│   │   ├── apt_packages.py
│   │   ├── build_info.py
│   │   ├── conda_environment.py
│   │   ├── julia_packages.py
│   │   ├── manifest_interface.py
│   │   ├── r_packages.py
│   │   └── spark_info.py
│   ├── taggers/
│   │   ├── __init__.py
│   │   ├── date.py
│   │   ├── sha.py
│   │   ├── tagger_interface.py
│   │   ├── ubuntu_version.py
│   │   └── versions.py
│   └── utils/
│       ├── __init__.py
│       ├── docker_runner.py
│       ├── get_platform.py
│       ├── get_prefix.py
│       ├── git_helper.py
│       └── quoted_output.py
├── tests/
│   ├── README.md
│   ├── __init__.py
│   ├── by_image/
│   │   ├── all-spark-notebook/
│   │   │   ├── data/
│   │   │   │   ├── local_sparkR.ipynb
│   │   │   │   └── local_sparklyr.ipynb
│   │   │   └── test_spark_r_nbconvert.py
│   │   ├── base-notebook/
│   │   │   ├── data/
│   │   │   │   └── check_listening.py
│   │   │   ├── test_container_options.py
│   │   │   ├── test_healthcheck.py
│   │   │   ├── test_ips.py
│   │   │   ├── test_notebook.py
│   │   │   ├── test_pandoc.py
│   │   │   └── test_start_container.py
│   │   ├── datascience-notebook/
│   │   │   ├── test_julia_datascience.py
│   │   │   ├── test_mimetypes.py
│   │   │   └── test_pluto_datascience.py
│   │   ├── docker-stacks-foundation/
│   │   │   ├── data/
│   │   │   │   └── run-hooks/
│   │   │   │       ├── change/
│   │   │   │       │   ├── a.sh
│   │   │   │       │   ├── b.sh
│   │   │   │       │   └── c.sh
│   │   │   │       ├── executables/
│   │   │   │       │   ├── executable.py
│   │   │   │       │   ├── non_executable.py
│   │   │   │       │   └── run-me.sh
│   │   │   │       ├── failures/
│   │   │   │       │   ├── a.sh
│   │   │   │       │   ├── b.py
│   │   │   │       │   ├── c.sh
│   │   │   │       │   └── d.sh
│   │   │   │       ├── sh-files/
│   │   │   │       │   ├── executable.sh
│   │   │   │       │   └── non-executable.sh
│   │   │   │       └── unset/
│   │   │   │           ├── a.sh
│   │   │   │           ├── b.sh
│   │   │   │           └── c.sh
│   │   │   ├── test_outdated.py
│   │   │   ├── test_package_managers.py
│   │   │   ├── test_packages.py
│   │   │   ├── test_python_version.py
│   │   │   ├── test_run_hooks.py
│   │   │   ├── test_units.py
│   │   │   └── test_user_options.py
│   │   ├── julia-notebook/
│   │   │   ├── test_julia.py
│   │   │   └── test_pluto.py
│   │   ├── minimal-notebook/
│   │   │   ├── data/
│   │   │   │   ├── notebook_math.ipynb
│   │   │   │   └── notebook_svg.ipynb
│   │   │   └── test_nbconvert.py
│   │   ├── pyspark-notebook/
│   │   │   ├── data/
│   │   │   │   ├── issue_1168.ipynb
│   │   │   │   └── local_pyspark.ipynb
│   │   │   ├── test_spark.py
│   │   │   ├── test_spark_nbconvert.py
│   │   │   └── units/
│   │   │       ├── unit_pandas_version.py
│   │   │       └── unit_spark.py
│   │   ├── pytorch-notebook/
│   │   │   └── units/
│   │   │       └── unit_pytorch.py
│   │   ├── r-notebook/
│   │   │   └── test_R_mimetypes.py
│   │   ├── scipy-notebook/
│   │   │   ├── data/
│   │   │   │   ├── cython/
│   │   │   │   │   ├── helloworld.pyx
│   │   │   │   │   └── setup.py
│   │   │   │   └── matplotlib/
│   │   │   │       ├── matplotlib_1.py
│   │   │   │       └── matplotlib_fonts_1.py
│   │   │   ├── test_cython.py
│   │   │   ├── test_extensions.py
│   │   │   ├── test_matplotlib.py
│   │   │   └── units/
│   │   │       └── unit_pandas.py
│   │   └── tensorflow-notebook/
│   │       └── units/
│   │           └── unit_tensorflow.py
│   ├── conftest.py
│   ├── hierarchy/
│   │   ├── __init__.py
│   │   ├── get_test_dirs.py
│   │   └── images_hierarchy.py
│   ├── pytest.ini
│   ├── run_tests.py
│   ├── shared_checks/
│   │   ├── R_mimetype_check.py
│   │   ├── __init__.py
│   │   ├── nbconvert_check.py
│   │   └── pluto_check.py
│   └── utils/
│       ├── __init__.py
│       ├── conda_package_helper.py
│       └── tracked_container.py
└── wiki/
    ├── Home.md
    ├── __init__.py
    ├── config.py
    ├── manifest_time.py
    └── update_wiki.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer/Dockerfile
================================================
FROM mcr.microsoft.com/devcontainers/python:3.13

COPY requirements-dev.txt /tmp/requirements-dev.txt
COPY docs/requirements.txt /tmp/requirements-docs.txt

RUN pip install --no-cache-dir -r /tmp/requirements-dev.txt && \
    pip install --no-cache-dir -r /tmp/requirements-docs.txt


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
  "name": "Jupyter Docker Stacks",
  "build": {
    "context": "..",
    "dockerfile": "Dockerfile"
  },

  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {
      "moby": false
    }
  },

  "postCreateCommand": "pre-commit install --install-hooks",

  "customizations": {
    "vscode": {
      "extensions": [
        "github.copilot-chat",
        "github.copilot",
        "github.vscode-github-actions",
        "github.vscode-pull-request-github",
        "ms-azuretools.vscode-containers",
        "ms-azuretools.vscode-docker",
        "ms-python.autopep8",
        "ms-vscode.makefile-tools"
      ]
    }
  }
}


================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 88
select = C, E, F, W, B, B950
extend-ignore = E203, E501, E704, W503


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf


================================================
FILE: .github/ISSUE_TEMPLATE/blank.yml
================================================
name: "(maintainers only) Blank issue"
description: For maintainers only
labels: []

body:
  - type: markdown
    attributes:
      value: |
        This is a blank issue template for maintainers to use as needed.

  - type: checkboxes
    attributes:
      label: Are you a maintainer?
      description: Please confirm you are a maintainer before proceeding.
      options:
        - label: Yes, I am a maintainer
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
labels: ["type:Bug"]

body:
  - type: markdown
    attributes:
      value: |
        Hi! Thanks for using the Jupyter Docker Stacks and taking some time to contribute to this project.

        We'd appreciate it if you could check out the [Troubleshooting common problems](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/troubleshooting.html) section in the documentation,
        as well as [existing issues](https://github.com/jupyter/docker-stacks/issues?q=is%3Aissue) prior to submitting an issue to avoid duplication.

        Please answer the following sections to help us troubleshoot the problem.

  - type: dropdown
    attributes:
      label: What docker image(s) are you using?
      description: Select as many images as applicable
      multiple: true
      options:
        - all-spark-notebook
        - base-notebook
        - datascience-notebook
        - docker-stacks-foundation
        - julia-notebook
        - minimal-notebook
        - pyspark-notebook
        - pytorch-notebook
        - r-notebook
        - scipy-notebook
        - tensorflow-notebook
    validations:
      required: true

  - type: input
    attributes:
      label: Host OS
      placeholder: |
        Example:
        Ubuntu 24.04
    validations:
      required: true

  - type: dropdown
    attributes:
      label: Host architecture
      options:
        - x86_64
        - aarch64
    validations:
      required: true

  - type: textarea
    attributes:
      label: What Docker command are you running?
      description: |
        What complete docker command do you run to launch the container (omitting sensitive values)?
      placeholder: |
        Example:
        `docker run -it --rm -p 8888:8888 quay.io/jupyter/base-notebook`
    validations:
      required: true

  - type: textarea
    attributes:
      label: How to Reproduce the problem?
      description: Please provide steps to reproduce this bug (once the container is running).
      placeholder: |
        Example:

        1. Visit <http://localhost:8888>

        2. Start an R notebook

        3. ...
    validations:
      required: true

  - type: textarea
    attributes:
      label: Command output
      render: bash session
      description: |
        Provide the output of the steps above, including the commands
        themselves and Docker's output/traceback etc. If you're familiar with
        Markdown, this block will have triple backticks added automatically
        around it -- you don't have to add them.

        If you want to present output from multiple commands, please present
        that as a shell session (commands you run get prefixed with `$ `).
        Please also ensure that the "How to reproduce" section contains matching
        instructions for reproducing this.

  - type: textarea
    attributes:
      label: Expected behavior
      description: |
        A clear and concise description of what you expected to happen.
      placeholder: |
        Example: `ggplot` output appears in my notebook.

  - type: textarea
    attributes:
      label: Actual behavior
      description: |
        A clear and concise description of what the bug is.
      placeholder: |
        Example: No output is visible in the notebook and the Server log contains messages about ...
    validations:
      required: true

  - type: textarea
    attributes:
      label: Anything else?
      description: |
        Links? References? Anything that will give us more context about the issue you are encountering!

        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
    validations:
      required: false

  - type: checkboxes
    attributes:
      label: Latest Docker version
      description: You should try to use the latest Docker version
      options:
        - label: I've updated my Docker version to the latest available, and the issue persists
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 📖 - Jupyter Docker Stacks documentation
    url: https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html
    about: Go to the project's documentation
  - name: 🔍 - Troubleshooting common problems
    url: https://jupyter-docker-stacks.readthedocs.io/en/latest/using/troubleshooting.html
    about: Documentation section on troubleshooting commonly encountered errors
  - name: 💬 - Jupyter community Discourse
    url: https://discourse.jupyter.org/
    about: Interact with the rest of the Jupyter community


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest a new feature for this project
labels: ["type:Enhancement"]

body:
  - type: markdown
    attributes:
      value: |
        Hi! Thanks for using the Jupyter Docker Stacks and taking some time to contribute to this project.

        We'd appreciate it if you could check out the [Suggesting a new feature](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/features.html#suggesting-a-new-feature)
        section in the documentation for our preferred processes before submitting a feature request.

  - type: dropdown
    attributes:
      label: What docker image(s) is this feature applicable to?
      description: Select as many images as applicable
      multiple: true
      options:
        - all-spark-notebook
        - base-notebook
        - datascience-notebook
        - docker-stacks-foundation
        - julia-notebook
        - minimal-notebook
        - pyspark-notebook
        - pytorch-notebook
        - r-notebook
        - scipy-notebook
        - tensorflow-notebook
        - new community stack
    validations:
      required: true

  - type: textarea
    attributes:
      label: What change(s) are you proposing?
      description: |
        Be concise and feel free to add supporting links or references.
      placeholder: |
        Example:
          - Add the [altair](https://altair-viz.github.io) package to the image.
    validations:
      required: true

  - type: textarea
    attributes:
      label: How does this affect the user?
      description: |
        How will the proposed feature affect the user's workflow?
        How will this feature make the image more robust, secure, etc.?
      placeholder: |
        Example:
          - Altair is a declarative statistical visualization library for Python, based on Vega and Vega-Lite, and the source is available on GitHub.
          - With Altair, you can spend more time understanding your data and its meaning.
          - Altair's API is simple, friendly, and consistent and built on top of the powerful Vega-Lite visualization grammar.
          - This elegant simplicity produces beautiful and effective visualizations with a minimal amount of code.
    validations:
      required: true

  - type: textarea
    attributes:
      label: Anything else?
      description: |
        Links? References? Anything that will give us more context about the feature you are proposing.
    validations:
      required: false


================================================
FILE: .github/actions/apply-single-tags/action.yml
================================================
name: Apply single platform tags
description: Download the image tar, load it to Docker and apply tags to it

inputs:
  image:
    description: Image name
    required: true
  platform:
    description: Image platform
    required: true
  variant:
    description: Variant tag prefix
    required: true

runs:
  using: composite
  steps:
    - name: Load image to Docker 📥
      uses: ./.github/actions/load-image
      with:
        image: ${{ inputs.image }}
        platform: ${{ inputs.platform }}
        variant: ${{ inputs.variant }}

    - name: Download tags file 📥
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
      with:
        name: ${{ inputs.platform }}-${{ inputs.variant }}-${{ inputs.image }}.txt
        path: /tmp/jupyter/tags/

    - name: Apply tags to the loaded image 🏷
      run: |
        python3 -m tagging.apps.apply_tags \
          --registry ${{ env.REGISTRY }} \
          --owner ${{ env.OWNER }} \
          --image ${{ inputs.image }} \
          --variant ${{ inputs.variant }} \
          --platform ${{ inputs.platform }} \
          --tags-dir /tmp/jupyter/tags/
      shell: bash

    - name: Upload SBOM for the image 🧾
      uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1
      with:
        image: ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }}
        artifact-name: ${{ inputs.image }}-${{ inputs.platform }}-${{ inputs.variant }}-sbom.spdx.json
        upload-artifact-retention: 40

    # This step is needed to prevent pushing non-multi-arch "latest" tag
    - name: Remove the "latest" tag from the image 🗑️
      run: docker image rmi ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }}:latest
      shell: bash

    - name: Show Docker images 📦
      run: docker image ls --all
      shell: bash


================================================
FILE: .github/actions/create-dev-env/action.yml
================================================
name: Build environment
description: Create a build environment

runs:
  using: composite
  steps:
    - name: Set Up Python 🐍
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: 3.12

    - name: Install Dev Dependencies 📦
      run: |
        pip install --upgrade pip
        pip install --upgrade -r requirements-dev.txt
      shell: bash

    # We need to have a recent docker version
    # More info: https://github.com/jupyter/docker-stacks/pull/2255
    # Can be removed after Docker Engine is updated
    # https://github.com/actions/runner-images/issues/11766
    - name: Set Up Docker 🐳
      uses: docker/setup-docker-action@1a6edb0ba9ac496f6850236981f15d8f9a82254d # v5.0.0
      with:
        set-host: true


================================================
FILE: .github/actions/free-disk-space/action.yml
================================================
name: "Free Disk Space (Ubuntu)"
description: "A GitHub Action to free up disk space on an Ubuntu GitHub Actions runner."

runs:
  using: "composite"
  steps:
    - name: Installing rmz
      shell: bash
      run: |
        curl -fsSL --tlsv1.2 --proto '=https' https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash > /dev/null 2>&1
        cargo binstall -qy rmz
        ln -s ~/.cargo/bin/rmz /usr/local/bin/rmz

    - name: Freeing up disk space
      shell: bash
      run: |
        sudo rmz -f /usr/local/lib/android || true
        sudo rmz -f /usr/share/dotnet || true
        sudo rmz -f /opt/ghc /usr/local/.ghcup || true
        sudo rmz -f /usr/share/swift || true
        sudo rmz -f /usr/share/miniconda || true
        sudo rmz -f "${AGENT_TOOLSDIRECTORY}" || true


================================================
FILE: .github/actions/load-image/action.yml
================================================
name: Load Docker image
description: Download the image tar and load it to Docker

inputs:
  image:
    description: Image name
    required: true
  platform:
    description: Image platform
    required: true
  variant:
    description: Variant tag prefix
    required: true

runs:
  using: composite
  steps:
    - name: Download built image 📥
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
      with:
        name: ${{ inputs.image }}-${{ inputs.platform }}-${{ inputs.variant }}.tar.zst
        path: /tmp/jupyter/images/

    - name: Load downloaded image to docker 📥
      run: |
        zstd \
          --uncompress \
          --stdout \
          --rm \
          /tmp/jupyter/images/${{ inputs.image }}-${{ inputs.platform }}-${{ inputs.variant }}.tar.zst \
          | docker load
      shell: bash

    - name: Show Docker images 📦
      run: docker image ls --all
      shell: bash


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference

# We're adding `[FAST_BUILD]` prefix to commit messages (this adds it to PR title)
# This triggers a faster build, see more info in `.github/workflows/docker.yml`

version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    commit-message:
      prefix: "[FAST_BUILD] "
  - package-ecosystem: github-actions
    directory: .github/actions/apply-single-tags/
    schedule:
      interval: weekly
    commit-message:
      prefix: "[FAST_BUILD] "
  - package-ecosystem: github-actions
    directory: .github/actions/create-dev-env/
    schedule:
      interval: weekly
    commit-message:
      prefix: "[FAST_BUILD] "
  # This action is only used for some images, so full build is required
  - package-ecosystem: github-actions
    directory: .github/actions/free-disk-space/
    schedule:
      interval: weekly
  - package-ecosystem: github-actions
    directory: .github/actions/load-image/
    schedule:
      interval: weekly
    commit-message:
      prefix: "[FAST_BUILD] "


================================================
FILE: .github/pull_request_template.md
================================================
## Describe your changes

## Issue ticket if applicable

<!-- Example - Fix: https://github.com/jupyter/docker-stacks/issues/0 -->

## Checklist (especially for first-time contributors)

- [ ] I have performed a self-review of my code
- [ ] If it is a core feature, I have added thorough tests
- [ ] I will try not to use force-push to make the review process easier for reviewers
- [ ] I have updated the documentation for significant changes

<!-- markdownlint-disable-file MD041 -->


================================================
FILE: .github/workflows/contributed-recipes.yml
================================================
name: Test the contributed recipes

env:
  REGISTRY: quay.io
  OWNER: ${{ github.repository_owner }}

on:
  schedule:
    # Images are rebuilt at 03:00 on Monday UTC
    # So we're testing recipes one hour in advance
    # They will also be tested after building images
    - cron: "0 2 * * 1"
  pull_request:
    paths:
      - ".github/workflows/contributed-recipes.yml"
      - "docs/using/recipe_code/**"
  push:
    branches:
      - main
    paths:
      - ".github/workflows/contributed-recipes.yml"
      - "docs/using/recipe_code/**"
  workflow_dispatch:
  workflow_call:
    inputs:
      # There is no good way to detect if the workflow was called using workflow_call
      # https://github.com/actions/runner/discussions/1884
      called-using-workflow-call:
        description: "Was the workflow called using workflow_call"
        required: true
        type: boolean

permissions:
  contents: read

jobs:
  generate-matrix:
    runs-on: ubuntu-24.04
    timeout-minutes: 1
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Calculate recipes matrix 🛠
        id: set-matrix
        run: docs/using/recipe_code/generate_matrix.py >> "${GITHUB_OUTPUT}"
        env:
          REPOSITORY_OWNER: ${{ github.repository_owner }}

  build:
    runs-on: ${{ matrix.runs-on }}
    timeout-minutes: 10
    needs: generate-matrix
    if: github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru'

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Load image to Docker 📥
        if: ${{ inputs.called-using-workflow-call && matrix.parent-image != '' }}
        uses: ./.github/actions/load-image
        with:
          image: ${{ matrix.parent-image }}
          platform: ${{ matrix.platform }}
          variant: default

      # Not pulling the image, because it might be loaded from previous step or will be downloaded automatically
      - name: Build recipe with parent image 🛠
        if: ${{ matrix.parent-image != '' }}
        run: |
          docker build \
            --rm --force-rm \
            --tag my-custom-image \
            -f ./${{ matrix.dockerfile }} \
            --build-arg BASE_IMAGE=${{ env.REGISTRY }}/${{ env.OWNER }}/${{ matrix.parent-image }} \
            ./
        env:
          DOCKER_BUILDKIT: 1
          # Full logs for CI build
          BUILDKIT_PROGRESS: plain
        working-directory: docs/using/recipe_code
        shell: bash

      # Not pulling the image, because it might be loaded from previous step or will be downloaded automatically
      - name: Build recipe without parent image 🛠
        if: ${{ matrix.parent-image == '' }}
        run: |
          docker build \
            --rm --force-rm \
            --tag my-custom-image \
            -f ./${{ matrix.dockerfile }} \
            ./
        env:
          DOCKER_BUILDKIT: 1
          # Full logs for CI build
          BUILDKIT_PROGRESS: plain
        working-directory: docs/using/recipe_code
        shell: bash

    strategy:
      matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}


================================================
FILE: .github/workflows/docker-build-test-upload.yml
================================================
name: Download a parent image, build a new one, and test it; upload the image, tags, build history line and manifest to GitHub artifacts

env:
  REGISTRY: quay.io
  OWNER: ${{ github.repository_owner }}

on:
  workflow_call:
    inputs:
      parent-image:
        description: Parent image name
        required: true
        type: string
      parent-variant:
        description: Parent variant tag prefix
        required: false
        type: string
        default: default
      image:
        description: Image name
        required: true
        type: string
      variant:
        description: Variant tag prefix
        required: false
        type: string
        default: default
      platform:
        description: Image platform
        required: true
        type: string
      runs-on:
        description: GitHub Actions Runner image
        required: true
        type: string
      timeout-minutes:
        description: Timeout in minutes
        required: true
        type: number

permissions:
  contents: read

jobs:
  build-test-upload:
    runs-on: ${{ inputs.runs-on }}
    timeout-minutes: ${{ inputs.timeout-minutes }}

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Compute commit hash tag 🏷
        id: hash
        run: echo "tag=${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
        shell: bash
      - name: Free disk space 🧹
        uses: ./.github/actions/free-disk-space
        if: contains(inputs.variant, 'cuda') || inputs.image == 'datascience-notebook' || inputs.image == 'all-spark-notebook'
      - name: Create dev environment 📦
        uses: ./.github/actions/create-dev-env

      - name: Load parent built image to Docker 📥
        if: inputs.parent-image != ''
        uses: ./.github/actions/load-image
        with:
          image: ${{ inputs.parent-image }}
          platform: ${{ inputs.platform }}
          variant: ${{ inputs.parent-variant }}

      - name: Pull base ubuntu image 📥
        if: inputs.parent-image == ''
        run: docker pull ubuntu:24.04
        shell: bash

      - name: Build image 🛠
        run: |
          docker build \
            --rm --force-rm \
            --tag ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }} \
            images/${{ inputs.image }}/${{ inputs.variant != 'default' && inputs.variant || '.' }}/ \
            --build-arg REGISTRY=${{ env.REGISTRY }} \
            --build-arg OWNER=${{ env.OWNER }}
        env:
          DOCKER_BUILDKIT: 1
          # Full logs for CI build
          BUILDKIT_PROGRESS: plain
        shell: bash

      - name: Write tags file 🏷
        run: |
          python3 -m tagging.apps.write_tags_file \
            --registry ${{ env.REGISTRY }} \
            --owner ${{ env.OWNER }} \
            --image ${{ inputs.image }} \
            --variant ${{ inputs.variant }} \
            --tags-dir /tmp/jupyter/tags/
        shell: bash
      - name: Upload tags file 💾
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          path: /tmp/jupyter/tags/${{ inputs.platform }}-${{ inputs.variant }}-${{ inputs.image }}.txt
          retention-days: 3
          archive: false

      - name: Write manifest and build history file 🏷
        run: |
          python3 -m tagging.apps.write_manifest \
            --registry ${{ env.REGISTRY }} \
            --owner ${{ env.OWNER }} \
            --image ${{ inputs.image }} \
            --variant ${{ inputs.variant }} \
            --hist-lines-dir /tmp/jupyter/hist_lines/ \
            --manifests-dir /tmp/jupyter/manifests/ \
            --repository ${{ github.repository }}
        shell: bash
      - name: Upload manifest file 💾
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          path: /tmp/jupyter/manifests/${{ inputs.platform }}-${{ inputs.variant }}-${{ inputs.image }}-${{ steps.hash.outputs.tag }}.md
          retention-days: 3
          archive: false
      - name: Upload build history line 💾
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          path: /tmp/jupyter/hist_lines/${{ inputs.platform }}-${{ inputs.variant }}-${{ inputs.image }}-${{ steps.hash.outputs.tag }}.txt
          retention-days: 3
          archive: false

      - name: Save image as a tar for later use 💾
        run: |
          mkdir -p /tmp/jupyter/images/
          docker save \
            ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }} \
            | zstd > /tmp/jupyter/images/${{ inputs.image }}-${{ inputs.platform }}-${{ inputs.variant }}.tar.zst
        shell: bash
      - name: Upload image as artifact 💾
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          path: /tmp/jupyter/images/${{ inputs.image }}-${{ inputs.platform }}-${{ inputs.variant }}.tar.zst
          retention-days: 3
          archive: false

      - name: Run tests ✅
        run: |
          python3 -m tests.run_tests \
            --registry ${{ env.REGISTRY }} \
            --owner ${{ env.OWNER }} \
            --image ${{ inputs.image }}
        shell: bash


================================================
FILE: .github/workflows/docker-tag-merge.yml
================================================
name: Merge single platform tags

env:
  REGISTRY: quay.io
  PUSH_TO_REGISTRY: ${{ (github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru') && (github.ref == 'refs/heads/main' || github.event_name == 'schedule') }}

on:
  workflow_call:
    inputs:
      image:
        description: Image name
        required: true
        type: string
      variant:
        description: Variant tag prefix
        required: true
        type: string
      timeout-minutes:
        description: Timeout in minutes
        default: 5
        type: number
    secrets:
      REGISTRY_USERNAME:
        required: true
      REGISTRY_TOKEN:
        required: true

permissions:
  contents: read

jobs:
  tag-merge:
    runs-on: ubuntu-24.04
    timeout-minutes: ${{ inputs.timeout-minutes }}

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Create dev environment 📦
        uses: ./.github/actions/create-dev-env

      - name: Download aarch64 tags file 🏷
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: aarch64-${{ inputs.variant }}-${{ inputs.image }}.txt
          path: /tmp/jupyter/tags/

      - name: Download x86_64 tags file 🏷
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: x86_64-${{ inputs.variant }}-${{ inputs.image }}.txt
          path: /tmp/jupyter/tags/

      - name: Login to Registry 🔐
        if: env.PUSH_TO_REGISTRY == 'true'
        run: |
          docker login ${{ env.REGISTRY }} \
            --username ${{ secrets.REGISTRY_USERNAME }} \
            --password ${{ secrets.REGISTRY_TOKEN }} || \
          docker login ${{ env.REGISTRY }} \
            --username ${{ secrets.REGISTRY_USERNAME }} \
            --password ${{ secrets.REGISTRY_TOKEN }}
        shell: bash
        id: login

      - name: Merge tags for the images 🔀
        run: |
          python3 -m tagging.apps.merge_tags \
            --image ${{ inputs.image }} \
            --variant ${{ inputs.variant }} \
            --tags-dir /tmp/jupyter/tags/
        shell: bash

      - name: Logout from Registry 🔐
        if: always() && env.PUSH_TO_REGISTRY == 'true' && steps.login.outcome == 'success'
        run: |
          docker logout ${{ env.REGISTRY }}
        shell: bash


================================================
FILE: .github/workflows/docker-tag-push-merge.yml
================================================
name: Download a Docker image and its tags from GitHub artifacts, apply them, and push the image to the Registry; then merge them

on:
  workflow_call:
    inputs:
      image:
        description: Image name
        required: true
        type: string
      variant:
        description: Variant tag prefix
        required: true
        type: string
    secrets:
      REGISTRY_USERNAME:
        required: true
      REGISTRY_TOKEN:
        required: true

permissions:
  contents: read

jobs:
  tag-push:
    uses: ./.github/workflows/docker-tag-push.yml
    with:
      image: ${{ inputs.image }}
      variant: ${{ inputs.variant }}
    secrets:
      REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
      REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

  tag-merge:
    uses: ./.github/workflows/docker-tag-merge.yml
    needs: tag-push
    with:
      image: ${{ inputs.image }}
      variant: ${{ inputs.variant }}
    secrets:
      REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
      REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}


================================================
FILE: .github/workflows/docker-tag-push.yml
================================================
name: Download a Docker image and its tags from GitHub artifacts, apply them, and push the image to the Registry

env:
  REGISTRY: quay.io
  OWNER: ${{ github.repository_owner }}
  PUSH_TO_REGISTRY: ${{ (github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru') && (github.ref == 'refs/heads/main' || github.event_name == 'schedule') }}

on:
  workflow_call:
    inputs:
      image:
        description: Image name
        required: true
        type: string
      variant:
        description: Variant tag prefix
        required: true
        type: string
      timeout-minutes:
        description: Timeout in minutes
        default: 25
        type: number
    secrets:
      REGISTRY_USERNAME:
        required: true
      REGISTRY_TOKEN:
        required: true

permissions:
  contents: read

jobs:
  tag-push:
    runs-on: ubuntu-24.04
    timeout-minutes: ${{ inputs.timeout-minutes }}
    strategy:
      matrix:
        platform: [aarch64, x86_64]

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - name: Free disk space 🧹
        uses: ./.github/actions/free-disk-space
        if: contains(inputs.variant, 'cuda') || inputs.image == 'datascience-notebook' || inputs.image == 'all-spark-notebook'
      - name: Create dev environment 📦
        uses: ./.github/actions/create-dev-env

      - name: Download image tar and apply tags 🏷
        uses: ./.github/actions/apply-single-tags
        with:
          image: ${{ inputs.image }}
          variant: ${{ inputs.variant }}
          platform: ${{ matrix.platform }}

      - name: Login to Registry 🔐
        if: env.PUSH_TO_REGISTRY == 'true'
        run: |
          docker login ${{ env.REGISTRY }} \
            --username ${{ secrets.REGISTRY_USERNAME }} \
            --password ${{ secrets.REGISTRY_TOKEN }} || \
          docker login ${{ env.REGISTRY }} \
            --username ${{ secrets.REGISTRY_USERNAME }} \
            --password ${{ secrets.REGISTRY_TOKEN }}
        shell: bash
        id: login

      - name: Push single platform images to Registry 📤
        if: env.PUSH_TO_REGISTRY == 'true'
        run: |
          docker push --all-tags ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }} || \
          docker push --all-tags ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }}
        shell: bash

      - name: Logout from Registry 🔐
        if: always() && env.PUSH_TO_REGISTRY == 'true' && steps.login.outcome == 'success'
        run: |
          docker logout ${{ env.REGISTRY }}
        shell: bash


================================================
FILE: .github/workflows/docker-wiki-update.yml
================================================
name: Download build history lines and manifests from GitHub artifacts and push them to the GitHub wiki
# We're doing everything in one workflow on purpose
# This way we make sure we don't access wiki pages from several jobs simultaneously

env:
  PUSH_TO_REGISTRY: ${{ github.ref == 'refs/heads/main' || github.event_name == 'schedule' }}

on:
  workflow_call:

permissions:
  contents: write

jobs:
  wiki-update:
    runs-on: ubuntu-24.04
    timeout-minutes: 1

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
      - name: Create dev environment 📦
        uses: ./.github/actions/create-dev-env

      - name: Compute commit hash tag 🏷
        id: hash
        run: echo "tag=${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
        shell: bash

      - name: Download all history lines 📥
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: "*-${{ steps.hash.outputs.tag }}.txt"
          path: /tmp/jupyter/hist_lines/
          merge-multiple: true

      - name: Download all manifests 📥
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: "*-${{ steps.hash.outputs.tag }}.md"
          path: /tmp/jupyter/manifests/
          merge-multiple: true

      - name: Checkout Wiki Repo 📃
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          repository: ${{ github.repository }}.wiki
          path: wiki_src/

      - name: Update wiki 🏷
        run: |
          python3 -m wiki.update_wiki \
            --wiki-dir wiki_src/ \
            --hist-lines-dir /tmp/jupyter/hist_lines/ \
            --manifests-dir /tmp/jupyter/manifests/ \
            --repository ${{ github.repository }}
        shell: bash

      - name: Push Wiki to GitHub 📤
        if: env.PUSH_TO_REGISTRY == 'true'
        uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
        with:
          commit_message: "Automated wiki publish for ${{ github.sha }}"
          repository: wiki_src/


================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker Stacks

# [FAST_BUILD] in the PR title makes this workflow only build
# the `jupyter/docker-stacks-foundation` and `jupyter/base-notebook` images
# This allows to run CI faster if a full build is not required
# This only works for a `pull_request` event and does not affect `push` to the `main` branch

on:
  schedule:
    # Weekly, at 03:00 on Monday UTC
    - cron: "0 3 * * 1"
  pull_request:
    paths:
      - ".github/workflows/docker.yml"
      # We use local reusable workflows to make architecture clean and simple
      # https://docs.github.com/en/actions/sharing-automations/reusing-workflows
      - ".github/workflows/docker-build-test-upload.yml"
      - ".github/workflows/docker-tag-merge.yml"
      - ".github/workflows/docker-tag-push-merge.yml"
      - ".github/workflows/docker-tag-push.yml"
      - ".github/workflows/docker-wiki-update.yml"

      # We use local composite actions to combine multiple workflow steps within one action
      # https://docs.github.com/en/actions/sharing-automations/creating-actions/about-custom-actions#composite-actions
      - ".github/actions/apply-single-tags/action.yml"
      - ".github/actions/create-dev-env/action.yml"
      - ".github/actions/free-disk-space/action.yml"
      - ".github/actions/load-image/action.yml"

      - "images/**"
      - "!images/*/README.md"
      - "tagging/**"
      - "!tagging/README.md"
      - "tests/**"
      - "!tests/README.md"
      - "wiki/**"
      - "requirements-dev.txt"
  push:
    branches:
      - main
    paths:
      - ".github/workflows/docker.yml"
      - ".github/workflows/docker-build-test-upload.yml"
      - ".github/workflows/docker-tag-merge.yml"
      - ".github/workflows/docker-tag-push-merge.yml"
      - ".github/workflows/docker-tag-push.yml"
      - ".github/workflows/docker-wiki-update.yml"

      - ".github/actions/apply-single-tags/action.yml"
      - ".github/actions/create-dev-env/action.yml"
      - ".github/actions/free-disk-space/action.yml"
      - ".github/actions/load-image/action.yml"

      - "images/**"
      - "!images/*/README.md"
      - "tagging/**"
      - "!tagging/README.md"
      - "tests/**"
      - "!tests/README.md"
      - "wiki/**"
      - "requirements-dev.txt"
  workflow_dispatch:

# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs
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

permissions:
  contents: read

jobs:
  aarch64-foundation:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: ""
      image: docker-stacks-foundation
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 10

  x86_64-foundation:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: ""
      image: docker-stacks-foundation
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 10

  aarch64-base:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: docker-stacks-foundation
      image: base-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 15
    needs: aarch64-foundation

  x86_64-base:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: docker-stacks-foundation
      image: base-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-foundation

  aarch64-minimal:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: base-notebook
      image: minimal-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 15
    needs: aarch64-base
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-minimal:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: base-notebook
      image: minimal-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-base
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-scipy:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: scipy-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 15
    needs: aarch64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-scipy:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: scipy-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-r:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: r-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 15
    needs: aarch64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-r:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: r-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-julia:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: julia-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      # This workflow sometimes takes quite long to build
      timeout-minutes: 30
    needs: aarch64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-julia:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: minimal-notebook
      image: julia-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 20
    needs: x86_64-minimal
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-tensorflow:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: tensorflow-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 15
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-tensorflow:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: tensorflow-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-tensorflow-cuda:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: tensorflow-notebook
      variant: cuda
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 25
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-tensorflow-cuda:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: tensorflow-notebook
      variant: cuda
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 25
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-pytorch:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 20
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-pytorch:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 20
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-pytorch-cuda12:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      variant: cuda12
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 25
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-pytorch-cuda12:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      variant: cuda12
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 25
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-pytorch-cuda13:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      variant: cuda13
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 25
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-pytorch-cuda13:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pytorch-notebook
      variant: cuda13
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 25
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-datascience:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: datascience-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      # This workflow sometimes takes quite long to build
      timeout-minutes: 30
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-datascience:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: datascience-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 25
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-pyspark:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pyspark-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 20
    needs: aarch64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-pyspark:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: scipy-notebook
      image: pyspark-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-scipy
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  aarch64-all-spark:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: pyspark-notebook
      image: all-spark-notebook
      platform: aarch64
      runs-on: ubuntu-24.04-arm
      timeout-minutes: 20
    needs: aarch64-pyspark
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  x86_64-all-spark:
    uses: ./.github/workflows/docker-build-test-upload.yml
    with:
      parent-image: pyspark-notebook
      image: all-spark-notebook
      platform: x86_64
      runs-on: ubuntu-24.04
      timeout-minutes: 15
    needs: x86_64-pyspark
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  contributed-recipes:
    uses: ./.github/workflows/contributed-recipes.yml
    with:
      called-using-workflow-call: true
    # Contributed recipes only use these images
    # If recipes using other images will be added, they should be added here as well
    #
    # contributed-recipes will give an error if the image is not yet built and uploaded
    needs: [aarch64-base, x86_64-base, aarch64-minimal, x86_64-minimal]

  tag-push-merge:
    uses: ./.github/workflows/docker-tag-push-merge.yml
    with:
      image: ${{ matrix.image }}
      variant: ${{ matrix.variant }}
    secrets:
      REGISTRY_USERNAME: ${{ secrets.QUAY_USERNAME }}
      REGISTRY_TOKEN: ${{ secrets.QUAY_ROBOT_TOKEN }}
    strategy:
      matrix:
        image:
          [
            docker-stacks-foundation,
            base-notebook,
            minimal-notebook,
            scipy-notebook,
            r-notebook,
            julia-notebook,
            tensorflow-notebook,
            pytorch-notebook,
            datascience-notebook,
            pyspark-notebook,
            all-spark-notebook,
          ]
        variant: [default]
        include:
          - image: tensorflow-notebook
            variant: cuda
          - image: pytorch-notebook
            variant: cuda12
          - image: pytorch-notebook
            variant: cuda13
    needs:
      [
        contributed-recipes,

        aarch64-foundation,
        aarch64-base,
        aarch64-minimal,
        aarch64-scipy,
        aarch64-r,
        aarch64-julia,
        aarch64-tensorflow,
        aarch64-tensorflow-cuda,
        aarch64-pytorch,
        aarch64-pytorch-cuda12,
        aarch64-pytorch-cuda13,
        aarch64-datascience,
        aarch64-pyspark,
        aarch64-all-spark,

        x86_64-foundation,
        x86_64-base,
        x86_64-minimal,
        x86_64-scipy,
        x86_64-r,
        x86_64-julia,
        x86_64-tensorflow,
        x86_64-tensorflow-cuda,
        x86_64-pytorch,
        x86_64-pytorch-cuda12,
        x86_64-pytorch-cuda13,
        x86_64-datascience,
        x86_64-pyspark,
        x86_64-all-spark,
      ]
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}

  tag-push-merge-fast:
    uses: ./.github/workflows/docker-tag-push-merge.yml
    with:
      image: ${{ matrix.image }}
      variant: ${{ matrix.variant }}
    secrets:
      REGISTRY_USERNAME: ${{ secrets.QUAY_USERNAME }}
      REGISTRY_TOKEN: ${{ secrets.QUAY_ROBOT_TOKEN }}
    strategy:
      matrix:
        image: [docker-stacks-foundation, base-notebook]
        variant: [default]
    needs: [aarch64-foundation, aarch64-base, x86_64-foundation, x86_64-base]
    if: contains(github.event.pull_request.title, '[FAST_BUILD]')

  wiki-update:
    uses: ./.github/workflows/docker-wiki-update.yml
    needs: tag-push-merge
    if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}
    permissions:
      contents: write

  wiki-update-fast:
    uses: ./.github/workflows/docker-wiki-update.yml
    needs: tag-push-merge-fast
    if: contains(github.event.pull_request.title, '[FAST_BUILD]')
    permissions:
      contents: write


================================================
FILE: .github/workflows/pre-commit.yml
================================================
name: Run pre-commit hooks

on:
  pull_request:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read

jobs:
  run-hooks:
    runs-on: ubuntu-24.04
    timeout-minutes: 5

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Set Up Python 🐍
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.12

      - name: Install pre-commit 📦
        run: |
          pip install --upgrade pip
          pip install --upgrade pre-commit

      - name: Run pre-commit hooks ✅
        run: pre-commit run --all-files --hook-stage manual


================================================
FILE: .github/workflows/registry-move.yml
================================================
name: Move some images from Docker Hub to Quay.io

env:
  OWNER: ${{ github.repository_owner }}
  PUSH_TO_REGISTRY: ${{ (github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru') && (github.ref == 'refs/heads/main') }}

on:
  pull_request:
    paths:
      - ".github/workflows/registry-move.yml"
  push:
    branches:
      - main
    paths:
      - ".github/workflows/registry-move.yml"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  registry-move:
    # To be able to use the latest skopeo
    runs-on: macos-latest
    timeout-minutes: 5
    if: github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru'

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install skopeo and Docker 📦
        run: |
          brew install skopeo
          brew install --cask docker

      - name: Login to Quay.io 🔐
        if: env.PUSH_TO_REGISTRY == 'true'
        run: |
          skopeo login quay.io \
            --username ${{ secrets.QUAY_USERNAME }} \
            --password ${{ secrets.QUAY_ROBOT_TOKEN }}

      - name: Move image from Docker Hub to Quay.io 🐳
        if: env.PUSH_TO_REGISTRY == 'true' && matrix.tag != 'tag-for-testing'
        run: |
          skopeo copy \
            --multi-arch all \
            docker://${{ env.OWNER }}/${{ matrix.image }}:${{ matrix.tag }} \
            docker://quay.io/${{ env.OWNER }}/${{ matrix.image }}:${{ matrix.tag }}

    strategy:
      fail-fast: false
      matrix:
        image:
          [
            docker-stacks-foundation,
            base-notebook,
            minimal-notebook,
            scipy-notebook,
            r-notebook,
            julia-notebook,
            tensorflow-notebook,
            pytorch-notebook,
            datascience-notebook,
            pyspark-notebook,
            all-spark-notebook,
          ]
        tag: [tag-for-testing]


================================================
FILE: .github/workflows/registry-overviews.yml
================================================
name: Update Registry overviews

env:
  OWNER: ${{ github.repository_owner }}

on:
  push:
    branches:
      - main
    paths:
      - ".github/workflows/registry-overviews.yml"

      - "images/*/README.md"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  update-overview:
    runs-on: ubuntu-24.04
    timeout-minutes: 1
    if: github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru'

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Push README to Registry 🐳
        uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
        env:
          DOCKER_APIKEY: ${{ secrets.APIKEY__QUAY_IO }}
        with:
          destination_container_repo: quay.io/${{ env.OWNER }}/${{ matrix.image }}
          provider: quay
          readme_file: images/${{ matrix.image }}/README.md

    strategy:
      matrix:
        image:
          [
            docker-stacks-foundation,
            base-notebook,
            minimal-notebook,
            scipy-notebook,
            r-notebook,
            julia-notebook,
            tensorflow-notebook,
            pytorch-notebook,
            datascience-notebook,
            pyspark-notebook,
            all-spark-notebook,
          ]


================================================
FILE: .github/workflows/sphinx.yml
================================================
name: Build Sphinx Documentation and check links

on:
  schedule:
    # Weekly, at 03:00 on Monday UTC
    - cron: "0 3 * * 1"
  pull_request:
    paths:
      - ".github/workflows/sphinx.yml"
      - "Makefile"

      - "docs/**"

      # These files are also rendered as docs pages
      - "README.md"
      - "CHANGELOG.md"

      # These files are used to generate some code snippets in the docs
      - "tagging/manifests/apt_packages.py"
      - "tagging/manifests/manifest_interface.py"
      - "tagging/taggers/sha.py"
      - "tagging/taggers/tagger_interface.py"
  push:
    branches:
      - main
    paths:
      - ".github/workflows/sphinx.yml"
      - "Makefile"

      - "docs/**"

      - "README.md"
      - "CHANGELOG.md"

      - "tagging/manifests/apt_packages.py"
      - "tagging/manifests/manifest_interface.py"
      - "tagging/taggers/sha.py"
      - "tagging/taggers/tagger_interface.py"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build-docs:
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    if: github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru' || github.event_name != 'schedule'

    steps:
      - name: Checkout Repo ⚡️
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: Set Up Python 🐍
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.12

      - name: Install Doc Dependencies 📦
        run: |
          pip install --upgrade pip
          pip install --upgrade -r docs/requirements.txt

      - name: Build Documentation 📖
        run: make docs

      - name: Check Documentation URLs 🔗
        run: make linkcheck-docs || make linkcheck-docs


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

##################################################################
#             The content above is copied from                   #
# https://github.com/github/gitignore/blob/main/Python.gitignore #
#       Please, add the content only below these lines           #
##################################################################

# Mac OS X
.DS_Store

# VS Code project configuration
.vscode/

# PyCharm project configuration
.idea/


================================================
FILE: .hadolint.yaml
================================================
---
ignored:
  - DL3006
  - DL3008
  - DL3013


================================================
FILE: .markdownlint.yaml
================================================
# Default state for all rules
default: true

# MD013/line-length - Line length
MD013:
  # Number of characters
  line_length: 200
  tables: false


================================================
FILE: .pre-commit-config.yaml
================================================
---
# pre-commit is a tool to perform a predefined set of tasks manually and/or
# automatically before git commits are made.
#
# Config reference: https://pre-commit.com/#pre-commit-configyaml---top-level
#
# Common tasks
#
# - Run on all files:   pre-commit run --all-files
# - Register git hooks: pre-commit install --install-hooks
#
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

exclude: ^LICENSE.md$

repos:
  # Autoupdate: Python code
  - repo: https://github.com/asottile/pyupgrade
    rev: 75992aaa40730136014f34227e0135f63fc951b4 # frozen: v3.21.2
    hooks:
      - id: pyupgrade
        args: [--py312-plus]

  # Automatically sort python imports
  - repo: https://github.com/PyCQA/isort
    rev: a333737ed43df02b18e6c95477ea1b285b3de15a # frozen: 8.0.1
    hooks:
      - id: isort
        args: [--profile, black]

  # Autoformat: Python code
  - repo: https://github.com/psf/black-pre-commit-mirror
    rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
    hooks:
      - id: black
        args: [--target-version=py312]

  # Check python code static typing
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: a66e98df7b4aeeb3724184b332785976d062b92e # frozen: v1.19.1
    hooks:
      - id: mypy
        args: [--config, ./mypy.ini]
        additional_dependencies:
          [
            "beautifulsoup4",
            "numpy",
            "pytest",
            "requests",
            "tenacity",
            "urllib3",
            "types-beautifulsoup4",
            "types-python-dateutil",
            "types-requests",
            "types-tabulate",
            "types-urllib3",
          ]
        # Unfortunately, `pre-commit` only runs on modified files
        # This doesn't work well with `mypy --follow-imports error`
        # See: https://github.com/pre-commit/mirrors-mypy/issues/34#issuecomment-1062160321
        #
        # To work around this we run `mypy` only in manual mode
        # So it won't run as part of `git commit` command,
        # but it will still be run as part of `pre-commit` workflow and give expected results
        stages: [manual]

  # Autoformat: YAML, JSON, Markdown, etc.
  - repo: https://github.com/rbubley/mirrors-prettier
    rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
    hooks:
      - id: prettier

  # `pre-commit sample-config` default hooks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
    hooks:
      - id: check-added-large-files
      - id: check-executables-have-shebangs
      - id: check-shebang-scripts-are-executable
      - id: end-of-file-fixer
      - id: requirements-txt-fixer
      - id: trailing-whitespace

  # Lint: Dockerfile
  - repo: https://github.com/hadolint/hadolint
    rev: 4e697ba704fd23b2409b947a319c19c3ee54d24f # frozen: v2.14.0
    hooks:
      - id: hadolint-docker
        entry: hadolint/hadolint:v2.14.0 hadolint

  # Lint: Dockerfile
  # We're linting .dockerfile files as well
  - repo: https://github.com/hadolint/hadolint
    rev: 4e697ba704fd23b2409b947a319c19c3ee54d24f # frozen: v2.14.0
    hooks:
      - id: hadolint-docker
        name: Lint *.dockerfile Dockerfiles
        entry: hadolint/hadolint:v2.12.1-beta hadolint
        types: [file]
        files: \.dockerfile$

  # Lint: YAML
  - repo: https://github.com/adrienverge/yamllint
    rev: cba56bcde1fdd01c1deb3f945e69764c291a6530 # frozen: v1.38.0
    hooks:
      - id: yamllint
        args: ["-d {extends: relaxed, rules: {line-length: disable}}", "-s"]

  # Lint: Bash scripts
  - repo: https://github.com/openstack/bashate
    rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1
    hooks:
      - id: bashate
        args: ["--ignore=E006"]

  # Lint: Shell scripts
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: 745eface02aef23e168a8afb6b5737818efbea95 # frozen: v0.11.0.1
    hooks:
      - id: shellcheck
        args: ["-x"]

  # Lint: Python
  - repo: https://github.com/PyCQA/flake8
    rev: d93590f5be797aabb60e3b09f2f52dddb02f349f # frozen: 7.3.0
    hooks:
      - id: flake8

  # Lint: Markdown
  - repo: https://github.com/DavidAnson/markdownlint-cli2
    rev: 5387279b3b4c24822c0f86d4df4f28b37e3e8992 # frozen: v0.21.0
    hooks:
      - id: markdownlint-cli2
        args: [--fix]

  # Strip output from Jupyter notebooks
  - repo: https://github.com/kynan/nbstripout
    rev: f5da19ce3b7b40e97c12ee9cd8ce97f48f97ddf7 # frozen: 0.9.1
    hooks:
      - id: nbstripout

  # nbQA provides tools from the Python ecosystem like
  # pyupgrade, isort, black, and flake8, adjusted for notebooks.
  - repo: https://github.com/nbQA-dev/nbQA
    rev: f96ec7f3b26a32619435686eb5813235f7e3327e # frozen: 1.9.1
    hooks:
      - id: nbqa-pyupgrade
        args: [--py312-plus]
      - id: nbqa-isort
      - id: nbqa-black
        args: [--target-version=py312]
      - id: nbqa-flake8

  # Run black on python code blocks in documentation files.
  - repo: https://github.com/adamchainz/blacken-docs
    rev: dda8db18cfc68df532abf33b185ecd12d5b7b326 # frozen: 1.20.0
    hooks:
      - id: blacken-docs
        # --skip-errors is added to allow us to have python syntax highlighting even if
        # the python code blocks include jupyter-specific additions such as % or !
        # See https://github.com/adamchainz/blacken-docs/issues/127 for an upstream
        # feature request about this.
        args: [--target-version=py312, --skip-errors]

# pre-commit.ci config reference: https://pre-commit.ci/#configuration
ci:
  autoupdate_schedule: monthly
  # Docker hooks do not work in pre-commit.ci
  # See: <https://github.com/pre-commit-ci/issues/issues/11>
  skip: [hadolint-docker]


================================================
FILE: .readthedocs.yaml
================================================
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
  os: ubuntu-22.04
  tools:
    python: "3.12"
    # You can also specify other tool versions:
    # nodejs: "20"
    # rust: "1.70"
    # golang: "1.20"
  jobs:
    post_checkout:
      - git fetch --unshallow || true

# Build documentation in the "docs/" directory with Sphinx
sphinx:
  configuration: docs/conf.py
  # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
  # builder: "dirhtml"
  # Fail on all warnings to avoid broken references
  # fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
#   - pdf
#   - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
  install:
    - requirements: docs/requirements.txt


================================================
FILE: CHANGELOG.md
================================================
# Changelog

This changelog only contains breaking and/or significant changes manually introduced to this repository (using Pull Requests).
All image manifests can be found in [the wiki](https://github.com/jupyter/docker-stacks/wiki).

## 2025-12-31

Affected: `pytorch-notebook`.

- **Non-breaking:** `pytorch-notebook`: Build pytorch cuda13 image instead of cuda11 ([#2391](https://github.com/jupyter/docker-stacks/pull/2391))

## 2025-12-02

Affected: `tensorflow-notebook`, `pytorch-notebook`.

- **Non-breaking:** Enable CUDA build for ARM64 ([#2352](https://github.com/jupyter/docker-stacks/pull/2352)).

## 2025-11-29

Affected: all images.

- **Breaking:** Use Docker v29 and `docker buildx imagetools create` ([#2368](https://github.com/jupyter/docker-stacks/pull/2368)).

## 2025-11-24

Affected: all images.

- **Non-breaking:** Add Dev Container support ([#2358](https://github.com/jupyter/docker-stacks/pull/2358)).
- **Non-breaking:** Add recipe on running Jupyter Docker Stacks with Singularity ([#2357](https://github.com/jupyter/docker-stacks/pull/2357)).

## 2025-11-06

Affected: `scipy-notebook`.

- **Breaking:** `scipy-notebook`: Remove facets package installation ([#2347](https://github.com/jupyter/docker-stacks/pull/2347)).

## 2025-09-16

Affected: all images.

- **Non-breaking:** Publish SBOM using anchore/sbom-action ([#2317](https://github.com/jupyter/docker-stacks/pull/2317)).

## 2025-08-15

Affected: all images.

- **Breaking:** `docker-stacks-foundation`: switch to Python 3.13 ([#2163](https://github.com/jupyter/docker-stacks/pull/2163)).

## 2025-04-13

Affected: `tensorflow-notebook`.

- **Non-breaking:** `tesnorflow-notebook`: Install latest tensorflow ([#2263](https://github.com/jupyter/docker-stacks/pull/2263)).

## 2025-04-12

Affected: all images.

- **Non-breaking:** `docker-stacks-foundation`: Pin libxml2 to avoid ABI breakage ([#2283](https://github.com/jupyter/docker-stacks/pull/2283)).

## 2025-04-11

Affected: all images.

- **Non-breaking:** Make docker tag-push depend on contributed recipes in CI ([#2282](https://github.com/jupyter/docker-stacks/pull/2282)).

## 2025-04-01

Affected: all images.

- **Non-breaking:** Apply and merge tags in the same place ([#2274](https://github.com/jupyter/docker-stacks/pull/2274)).

## 2025-03-23

Affected: `tensorflow-notebook`.

- **Non-breaking:** `tensorflow-notebook`: Use mamba to install jupyter-server-proxy ([#2262](https://github.com/jupyter/docker-stacks/pull/2262)).

## 2025-03-22

Affected: all images.

- **Non-breaking:** Use tty for running docker commands by default ([#2260](https://github.com/jupyter/docker-stacks/pull/2260)).
- **Non-breaking:** Improve logs around running docker ([#2261](https://github.com/jupyter/docker-stacks/pull/2261)).

## 2025-03-21

Affected: all images.

- **Non-breaking:** Refactor TrackedContainer run_detached/exec_cmd functions ([#2256](https://github.com/jupyter/docker-stacks/pull/2256)).
- **Non-breaking:** Do not allocate TTY in tests if not needed ([#2257](https://github.com/jupyter/docker-stacks/pull/2257)).
- **Non-breaking:** `base-notebook`: Flush output in Python before running execvp ([#2258](https://github.com/jupyter/docker-stacks/pull/2258)).

## 2025-03-20

Affected: all images except `docker-stacks-foundation`.

- **Non-breaking:** `base-notebook`: Refactor healthcheck tests to use one function ([#2254](https://github.com/jupyter/docker-stacks/pull/2254)).
- **Non-breaking:** `base-notebook`: Test server listening on IPv4/IPv6 ([#2255](https://github.com/jupyter/docker-stacks/pull/2255)).

## 2025-03-12

Affected: all images.

- **Non-breaking:** Add `conda` and `mamba` version taggers ([#2251](https://github.com/jupyter/docker-stacks/pull/2251)).
- **Non-breaking:** Make taggers and manifests functions ([#2252](https://github.com/jupyter/docker-stacks/pull/2252)).

## 2025-02-21

Affected: all images.

- **Non-breaking:** Better tagging directory structure ([#2228](https://github.com/jupyter/docker-stacks/pull/2228)).
- **Non-breaking:** Better testing directory structure ([#2231](https://github.com/jupyter/docker-stacks/pull/2231)).

## 2025-02-18

Affected: all images.

- **Non-breaking:** switch from `ubuntu-22.04-arm` to `ubuntu-24.04-arm` runners ([#2209](https://github.com/jupyter/docker-stacks/pull/2209)).
- **Non-breaking:** don't create extra free space in runners for cuda images ([#2218](https://github.com/jupyter/docker-stacks/pull/2218)).
- **Non-breaking:** revert "Pin some packages to fix `r-notebook` and `datascience-notebook` under aarch64" ([#2220](https://github.com/jupyter/docker-stacks/pull/2220)).
- **Non-breaking:** Simplify and improve `test_packages.py` ([#2219](https://github.com/jupyter/docker-stacks/pull/2219)).
- **Non-breaking:** Use Python 3.12 for internal code ([#2222](https://github.com/jupyter/docker-stacks/pull/2222)).

## 2025-02-17

Affected: all images.

- **Non-breaking:** build contributed recipes in PRs ([#2212](https://github.com/jupyter/docker-stacks/pull/2212), [#2213](https://github.com/jupyter/docker-stacks/pull/2213)).
- **Non-breaking:** remove information about Docker Hub images from Quay.io READMEs ([#2211](https://github.com/jupyter/docker-stacks/pull/2211)).
- **Non-breaking:** first upload artifacts and then run tests to make sure we can easily debug broken images ([#2214](https://github.com/jupyter/docker-stacks/pull/2214)).
- **Non-Breaking:** aarch64 `r-notebook`, `datascience-notebook`: pin some packages to fix `r-notebook` and `datascience-notebook` under aarch64 ([#2215](https://github.com/jupyter/docker-stacks/pull/2215)).
- **Non-breaking:** don't use matrix.image-variant, use 2 separate variables ([#2217](https://github.com/jupyter/docker-stacks/pull/2217)).

## 2025-02-11

Affected: all images.

- **Non-breaking:** start using `ubuntu-22.04-arm` GitHub-hosted `aarch64` runners ([#2202](https://github.com/jupyter/docker-stacks/pull/2202)).

## 2024-12-03

Affected: all images.

- **Breaking:** `docker-stacks-foundation`: switch to `mamba` v2 ([#2147](https://github.com/jupyter/docker-stacks/pull/2147)).
  More information about changes made: <https://mamba.readthedocs.io/en/latest/developer_zone/changes-2.0.html>.

## 2024-11-08

Affected: all images except `docker-stacks-foundation`.

- **Breaking:** `base-notebook`: stop installing `nodejs` from `conda-forge` ([#2172](https://github.com/jupyter/docker-stacks/pull/2172)).

  Reason: It isn't a direct dependency on anything in the images anymore, and increased the image size by ~150MB.

## 2024-11-06

Affected: all images except `docker-stacks-foundation`.

- **Non-breaking:** `base-notebook`: install `jupyterhub-base` and `nodejs` packages instead of `jupyterhub` package ([#2171](https://github.com/jupyter/docker-stacks/pull/2171)).

## 2024-10-23

Affected: all images.

- **Breaking:** `docker-stacks-foundation`: switch to Python 3.12 ([#2072](https://github.com/jupyter/docker-stacks/pull/2072)).

## 2024-10-22

Affected: `pyspark-notebook` and `all-spark-notebook` images.

- **Breaking:** `pyspark-notebook`: start using Spark 4.0.0 preview versions ([#2159](https://github.com/jupyter/docker-stacks/pull/2159)).
  `sparklyr` doesn't seem to support Spark v4 yet when using Spark locally.

  Reason: Spark v3 is not compatible with Python 3.12, and [the voting group has decided](https://github.com/jupyter/docker-stacks/pull/2072#issuecomment-2414123851) to switch to Spark v4 preview version.

## 2024-10-09

Affected: users building a custom set of images.

- **Breaking:** rename: `ROOT_CONTAINER`->`ROOT_IMAGE`, `BASE_CONTAINER`->`BASE_IMAGE` ([#2154](https://github.com/jupyter/docker-stacks/issues/2154), [#2155](https://github.com/jupyter/docker-stacks/pull/2155)).


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Project `jupyter/docker-stacks` Code of Conduct

Please see the [Project Jupyter Code of Conduct](https://github.com/jupyter/governance/blob/HEAD/conduct/code_of_conduct.md).


================================================
FILE: CONTRIBUTING.md
================================================
Thanks for contributing!
Please see the **Contributor Guide** section in [the documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/)
for information about how to contribute
[issues](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/issues.html),
[features](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/features.html),
[recipes](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/recipes.html),
[tests](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/tests.html),
and [community-maintained stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/stacks.html).

## Our Copyright Policy

Jupyter uses a shared copyright model. Each contributor maintains copyright
over their contributions to Jupyter. But, it is important to note that these
contributions are typically only changes to the repositories. Thus, the Jupyter
source code, in its entirety is not the copyright of any single person or
institution. Instead, it is the collective copyright of the entire Jupyter
Development Team. If individual contributors want to maintain a record of what
changes/contributions they have specific copyright on, they should indicate
their copyright in the commit message of the change, when they commit the
change to one of the Jupyter repositories.

With this in mind, the following banner should be used in any source code file
to indicate the copyright and license terms:

```text
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
```

<!-- markdownlint-disable-file MD041 -->


================================================
FILE: LICENSE.md
================================================
BSD 3-Clause License

Copyright (c) 2001-2015, IPython Development Team

Copyright (c) 2015-, Jupyter Development Team

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: Makefile
================================================
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
.PHONY: docs help test

SHELL:=bash
REGISTRY?=quay.io
OWNER?=jupyter

# Enable BuildKit for Docker build
export DOCKER_BUILDKIT:=1

# All the images listed in the build dependency order
ALL_IMAGES:= \
	docker-stacks-foundation \
	base-notebook \
	minimal-notebook \
	scipy-notebook \
	r-notebook \
	julia-notebook \
	tensorflow-notebook \
	pytorch-notebook \
	datascience-notebook \
	pyspark-notebook \
	all-spark-notebook



# https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help:
	@echo "jupyter/docker-stacks"
	@echo "====================="
	@echo "Replace % with a stack directory name (e.g., make build/minimal-notebook)"
	@echo
	@grep -E '^[a-zA-Z0-9_%/-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'



# Note that `ROOT_IMAGE` and `PYTHON_VERSION` arguments are only applicable to the `docker-stacks-foundation` image
build/%: DOCKER_BUILD_ARGS?=
build/%: ROOT_IMAGE?=ubuntu:24.04
build/%: PYTHON_VERSION?=3.13
build/%: ## build the latest image for a stack using the system's architecture
	docker build $(DOCKER_BUILD_ARGS) --rm --force-rm \
	  --tag "$(REGISTRY)/$(OWNER)/$(notdir $@)" \
	  "./images/$(notdir $@)" \
	  --build-arg REGISTRY="$(REGISTRY)" \
	  --build-arg OWNER="$(OWNER)" \
	  --build-arg ROOT_IMAGE="$(ROOT_IMAGE)" \
	  --build-arg PYTHON_VERSION="$(PYTHON_VERSION)"
	@echo -n "Built image size: "
	@docker images "$(REGISTRY)/$(OWNER)/$(notdir $@):latest" --format "{{.Size}}"
build-all: $(foreach I, $(ALL_IMAGES), build/$(I)) ## build all stacks



check-outdated/%: ## check the outdated mamba/conda packages in a stack and produce a report
	pytest tests/by_image/docker-stacks-foundation/test_outdated.py \
	  --registry "$(REGISTRY)" \
	  --owner "$(OWNER)" \
	  --image "$(notdir $@)"
check-outdated-all: $(foreach I, $(ALL_IMAGES), check-outdated/$(I)) ## check all the stacks for outdated packages



cont-stop-all: ## stop all containers
	@echo "Stopping all containers ..."
	-docker stop --time 0 $(shell docker ps --all --quiet) 2> /dev/null
cont-rm-all: ## remove all containers
	@echo "Removing all containers ..."
	-docker rm --force $(shell docker ps --all --quiet) 2> /dev/null
cont-clean-all: cont-stop-all cont-rm-all ## clean all containers (stop + rm)



docs: ## build HTML documentation
	sphinx-build -W --keep-going --color docs/ docs/_build/
linkcheck-docs: ## check for broken links
	sphinx-build -W --keep-going --color -b linkcheck docs/ docs/_build/



hook/%: VARIANT?=default
hook/%: REPOSITORY?=$(OWNER)/docker-stacks
hook/%: ## run post-build hooks for an image
	python3 -m tagging.apps.write_tags_file \
	  --registry "$(REGISTRY)" \
	  --owner "$(OWNER)" \
	  --image "$(notdir $@)" \
	  --variant "$(VARIANT)" \
	  --tags-dir /tmp/jupyter/tags/
	python3 -m tagging.apps.write_manifest \
	  --registry "$(REGISTRY)" \
	  --owner "$(OWNER)" \
	  --image "$(notdir $@)" \
	  --variant "$(VARIANT)" \
	  --hist-lines-dir /tmp/jupyter/hist_lines/ \
	  --manifests-dir /tmp/jupyter/manifests/ \
	  --repository "$(REPOSITORY)"
	python3 -m tagging.apps.apply_tags \
	  --registry "$(REGISTRY)" \
	  --owner "$(OWNER)" \
	  --image "$(notdir $@)" \
	  --variant "$(VARIANT)" \
	  --platform "$(shell uname -m)" \
	  --tags-dir /tmp/jupyter/tags/
hook-all: $(foreach I, $(ALL_IMAGES), hook/$(I)) ## run post-build hooks for all images



img-list: ## list jupyter images
	@echo "Listing $(OWNER) images ..."
	docker images "$(OWNER)/*"
	docker images "*/$(OWNER)/*"
img-rm-dang: ## remove dangling images (tagged None)
	@echo "Removing dangling images ..."
	-docker rmi --force $(shell docker images -f "dangling=true" --quiet) 2> /dev/null
img-rm-jupyter: ## remove jupyter images
	@echo "Removing $(OWNER) images ..."
	-docker rmi --force $(shell docker images --quiet "$(OWNER)/*") 2> /dev/null
	-docker rmi --force $(shell docker images --quiet "*/$(OWNER)/*") 2> /dev/null
img-rm: img-rm-dang img-rm-jupyter ## remove dangling and jupyter images



pull/%: ## pull a jupyter image
	docker pull "$(REGISTRY)/$(OWNER)/$(notdir $@)"
pull-all: $(foreach I, $(ALL_IMAGES), pull/$(I)) ## pull all images
push/%: ## push all tags for a jupyter image
	docker push --all-tags "$(REGISTRY)/$(OWNER)/$(notdir $@)"
push-all: $(foreach I, $(ALL_IMAGES), push/$(I)) ## push all tagged images



run-shell/%: ## run a bash in interactive mode in a stack
	docker run -it --rm "$(REGISTRY)/$(OWNER)/$(notdir $@)" $(SHELL)
run-sudo-shell/%: ## run bash in interactive mode as root in a stack
	docker run -it --rm --user root "$(REGISTRY)/$(OWNER)/$(notdir $@)" $(SHELL)



test/%: ## run tests against a stack
	python3 -m tests.run_tests \
	  --registry "$(REGISTRY)" \
	  --owner "$(OWNER)" \
	  --image "$(notdir $@)"
test-all: $(foreach I, $(ALL_IMAGES), test/$(I)) ## test all stacks


================================================
FILE: README.md
================================================
# Jupyter Docker Stacks

[![GitHub Actions badge](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml/badge.svg)](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml?query=branch%3Amain "Docker images build status")
[![Read the Docs badge](https://img.shields.io/readthedocs/jupyter-docker-stacks.svg)](https://jupyter-docker-stacks.readthedocs.io/en/latest/ "Documentation build status")
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/jupyter/docker-stacks/main.svg)](https://results.pre-commit.ci/latest/github/jupyter/docker-stacks/main "pre-commit.ci build status")
[![Discourse badge](https://img.shields.io/discourse/users.svg?color=%23f37626&server=https%3A%2F%2Fdiscourse.jupyter.org)](https://discourse.jupyter.org/ "Jupyter Discourse Forum")
[![Binder badge](https://static.mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyter/docker-stacks/main?urlpath=lab/tree/README.ipynb "Launch a quay.io/jupyter/base-notebook container on mybinder.org")

Jupyter Docker Stacks are a set of ready-to-run [Docker images](https://quay.io/organization/jupyter) containing Jupyter applications and interactive computing tools.
You can use a stack image to do any of the following (and more):

- Start a personal Jupyter Server with the JupyterLab frontend (default)
- Run JupyterLab for a team using JupyterHub
- Start a personal Jupyter Server with the Jupyter Notebook frontend in a local Docker container
- Write your own project Dockerfile

## Quick Start

You can [try the quay.io/jupyter/base-notebook image](https://mybinder.org/v2/gh/jupyter/docker-stacks/main?urlpath=lab/tree/README.ipynb) on <https://mybinder.org>.
Otherwise, the examples below may help you get started if you [have Docker installed](https://docs.docker.com/get-started/get-docker/),
know [which Docker image](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html) you want to use, and want to launch a single Jupyter Application in a container.

The [User Guide on ReadTheDocs](https://jupyter-docker-stacks.readthedocs.io/en/latest/) describes additional uses and features in detail.

```{note}
Since `2023-10-20` our images are only pushed to `Quay.io` registry.
Older images are available on Docker Hub, but they will no longer be updated.
```

### Example 1

This command pulls the `jupyter/scipy-notebook` image tagged `2025-12-31` from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the container's internal port `8888` to port `10000` of the host machine:

```bash
docker run -p 10000:8888 quay.io/jupyter/scipy-notebook:2025-12-31
```

You can modify the port on which the container's port is exposed by [changing the value of the `-p` option](https://docs.docker.com/engine/containers/run/#exposed-ports) to `-p 8888:8888`.

Visiting `http://<hostname>:10000/?token=<token>` in a browser loads JupyterLab,
where:

- The `hostname` is the name of the computer running Docker
- The `token` is the secret token printed in the console.

The container remains intact for restart after the Server exits.

### Example 2

This command pulls the `jupyter/datascience-notebook` image tagged `2025-12-31` from Quay.io if it is not already present on the local host.
It then starts an _ephemeral_ container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 10000.

```bash
docker run -it --rm -p 10000:8888 -v "${PWD}":/home/jovyan/work quay.io/jupyter/datascience-notebook:2025-12-31
```

The use of the `-v` flag in the command mounts the current working directory on the host (`${PWD}` in the example command) as `/home/jovyan/work` in the container.
The server logs appear in the terminal.

Visiting `http://<hostname>:10000/?token=<token>` in a browser loads JupyterLab.

Due to the usage of [the `--rm` flag](https://docs.docker.com/reference/cli/docker/container/run/#rm)
Docker automatically cleans up the container and removes the file system when the container exits,
but any changes made to the `~/work` directory and its files in the container will remain intact on the host.
[The `-i` flag](https://docs.docker.com/reference/cli/docker/container/run/#interactive) keeps the container's `STDIN` open, and lets you send input to the container through standard input.
[The `-t` flag](https://docs.docker.com/reference/cli/docker/container/run/#tty) attaches a pseudo-TTY to the container.

```{note}
By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`.
So, new notebooks will be saved there, unless you change the directory in the file browser.

To change the default directory, you must specify `ServerApp.root_dir` by adding this line to the previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`.
```

## Choosing Jupyter frontend

JupyterLab is the default for all the Jupyter Docker Stacks images.
It is still possible to switch back to Jupyter Notebook (or to launch a different startup command).
You can achieve this by passing the environment variable `DOCKER_STACKS_JUPYTER_CMD=notebook` (or any other valid `jupyter` subcommand) at container startup;
more information is available in the [documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#alternative-commands).

## Resources

- [Documentation on ReadTheDocs](https://jupyter-docker-stacks.readthedocs.io/en/latest/)
- [Issue Tracker on GitHub](https://github.com/jupyter/docker-stacks/issues)
- [Jupyter Discourse Forum](https://discourse.jupyter.org/)
- [Jupyter Website](https://jupyter.org)
- [Images on Quay.io](https://quay.io/organization/jupyter)

## Acknowledgments

- Starting from `2022-07-05`, `aarch64` self-hosted runners were sponsored by [`@mathbunnyru`](https://github.com/mathbunnyru/).
  Please, consider [sponsoring his work](https://github.com/sponsors/mathbunnyru) on GitHub
- Starting from `2023-10-31`, `aarch64` self-hosted runners are sponsored by an amazing [`2i2c non-profit organization`](https://2i2c.org)
- Starting from `2025-02-11`, we use GitHub-hosted `aarch64` runners

## CPU Architectures

- We publish containers for both `x86_64` and `aarch64` platforms
- Single-platform images have either `aarch64-` or `x86_64-` tag prefixes, for example, `quay.io/jupyter/base-notebook:aarch64-python-3.11.6`
- Starting from `2022-09-21`, we create multi-platform images (except `tensorflow-notebook`)
- Starting from `2023-06-01`, we create a multi-platform `tensorflow-notebook` image as well
- Starting from `2024-02-24`, we create CUDA enabled variants of `pytorch-notebook` image for `x86_64` platform
- Starting from `2024-03-26`, we create CUDA enabled variant of `tensorflow-notebook` image for `x86_64` platform

## Using old images

[![Python versions badge](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://www.python.org/downloads/ "Python versions supported")

This project only builds one set of images at a time.
If you want to use the older `Ubuntu` and/or `Python` version, you can use the following images:

| Build Date   | Ubuntu | Python | Tag            |
| ------------ | ------ | ------ | -------------- |
| 2022-10-09   | 20.04  | 3.7    | `1aac87eb7fa5` |
| 2022-10-09   | 20.04  | 3.8    | `a374cab4fcb6` |
| 2022-10-09   | 20.04  | 3.9    | `5ae537728c69` |
| 2022-10-09   | 20.04  | 3.10   | `f3079808ca8c` |
| 2022-10-09   | 22.04  | 3.7    | `b86753318aa1` |
| 2022-10-09   | 22.04  | 3.8    | `7285848c0a11` |
| 2022-10-09   | 22.04  | 3.9    | `ed2908bbb62e` |
| 2023-05-30   | 22.04  | 3.10   | `4d70cf8da953` |
| 2024-08-26   | 22.04  | 3.11   | `00987883e58d` |
| 2024-10-22   | 24.04  | 3.11   | `b74418220768` |
| 2025-08-11   | 24.04  | 3.12   | `82d322f00937` |
| weekly build | 24.04  | 3.13   | `latest`       |

## Contributing

Please see the [the documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/)
for information about how to contribute
[issues](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/issues.html),
[features](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/features.html),
[recipes](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/recipes.html),
[tests](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/tests.html),
and [community-maintained stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/contributing/stacks.html).

## LICENSE

This project is licensed under the terms of the Modified BSD License (also known as New or Revised or 3-Clause BSD).

## About the Jupyter Development Team

The Jupyter Development Team is the set of all contributors to the Jupyter project.
This includes all of the Jupyter subprojects.

The core team that coordinates development on GitHub can be found here:
<https://github.com/jupyter/>.

## Our Copyright Policy

Jupyter uses a shared copyright model. Each contributor maintains copyright
over their contributions to Jupyter. But, it is important to note that these
contributions are typically only changes to the repositories. Thus, the Jupyter
source code, in its entirety is not the copyright of any single person or
institution. Instead, it is the collective copyright of the entire Jupyter
Development Team. If individual contributors want to maintain a record of what
changes/contributions they have specific copyright on, they should indicate
their copyright in the commit message of the change, when they commit the
change to one of the Jupyter repositories.

With this in mind, the following banner should be used in any source code file
to indicate the copyright and license terms:

```text
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
```

## Alternatives

- [b-data](https://github.com/b-data)'s JupyterLab docker stacks - For
  [R](https://github.com/b-data/jupyterlab-r-docker-stack),
  [Python](https://github.com/b-data/jupyterlab-python-docker-stack),
  [MAX/Mojo](https://github.com/b-data/jupyterlab-mojo-docker-stack) and
  [Julia](https://github.com/b-data/jupyterlab-julia-docker-stack).
  With [code-server](https://github.com/coder/code-server) next to JupyterLab.
  Just Python – no [Conda](https://github.com/conda/conda) /
  [Mamba](https://github.com/mamba-org/mamba).
- [rocker/binder](https://rocker-project.org/images/versioned/binder.html) -
  From the R focused [rocker-project](https://rocker-project.org),
  lets you run both RStudio and Jupyter either standalone or in a JupyterHub
- [jupyter/repo2docker](https://github.com/jupyterhub/repo2docker) -
  Turn git repositories into Jupyter-enabled Docker Images
- [openshift/source-to-image](https://github.com/openshift/source-to-image) -
  A tool for building artifacts from source code and injecting them into docker images
- [jupyter-on-openshift/jupyter-notebooks](https://github.com/jupyter-on-openshift/jupyter-notebooks) -
  OpenShift compatible S2I builder for basic notebook images


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

Jupyter Docker Stacks only provides security updates for the latest version of each image.

## Reporting a Vulnerability

The Jupyter Vulnerability Handling Process is described in detail in the [security documentation](https://github.com/jupyter/security/blob/main/docs/vulnerability-handling.md).


================================================
FILE: binder/Dockerfile
================================================
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

# https://quay.io/repository/jupyter/base-notebook?tab=tags
ARG REGISTRY=quay.io
ARG OWNER=jupyter
ARG BASE_IMAGE=$REGISTRY/$OWNER/base-notebook:2025-12-31
FROM $BASE_IMAGE

LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"

# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

ENV TAG="2025-12-31"

COPY --chown=${NB_UID}:${NB_GID} binder/README.ipynb "${HOME}"/README.ipynb

RUN jupyter labextension disable "@jupyterlab/apputils-extension:announcements"


================================================
FILE: binder/README.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# jupyter/base-notebook on Binder\n",
    "\n",
    "Run the cells below to inspect what's in the [jupyter/base-notebook](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#jupyter-base-notebook) image from the Jupyter Docker Stacks project.\n",
    "\n",
    "You can launch the classic notebook interface in Binder by replacing `lab/tree/*` with `tree/` in the JupyterLab URL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "print(\n",
    "    f\"This container is using tag {os.environ['TAG']} of the jupyter/base-notebook image\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Server is running as the following user."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!id"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the contents of that user's home directory, the default notebook directory for the server."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!ls -al"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`mamba` is available in the user's path."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!which mamba"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The user has read/write access to the root mamba environment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!ls -l /opt/conda"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following packages are mamba-installed in the base image to support [Jupyter Notebook](https://github.com/jupyter/notebook), [JupyterLab](https://github.com/jupyterlab/jupyterlab), and their use in [JupyterHub](https://github.com/jupyterhub/jupyterhub) environments (e.g., [MyBinder](https://mybinder.org/))."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!mamba list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Other images in the [jupyter/docker-stacks project](https://github.com/jupyter/docker-stacks) include additional libraries. See the [Jupyter Docker Stacks documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/) for full details."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}


================================================
FILE: docs/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "docker-stacks"
copyright = "2025, Project Jupyter"
author = "Project Jupyter"

version = "latest"
release = "latest"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = []

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

language = "en"

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "alabaster"
html_static_path = ["_static"]

# The file above was generated using sphinx 8.1.3 with this command:
# sphinx-quickstart --project "docker-stacks" --author "Project Jupyter" -v "latest" -r "latest" -l en --no-sep --no-makefile --no-batchfile
# These are custom options for this project

html_theme = "sphinx_book_theme"
html_title = "Docker Stacks documentation"
html_logo = "_static/jupyter-logo.svg"
html_theme_options = {
    "logo": {
        "text": html_title,
    },
    "navigation_with_keys": False,
    "path_to_docs": "docs",
    "repository_branch": "main",
    "repository_url": "https://github.com/jupyter/docker-stacks",
    "use_download_button": True,
    "use_edit_page_button": True,
    "use_issues_button": True,
    "use_repository_button": True,
}
html_last_updated_fmt = "%Y-%m-%d"

extensions = ["myst_parser", "sphinx_copybutton", "sphinx_last_updated_by_git"]
source_suffix = {
    ".rst": "restructuredtext",
    ".md": "markdown",
}
pygments_style = "sphinx"

# MyST configuration reference: https://myst-parser.readthedocs.io/en/latest/configuration.html
myst_heading_anchors = 3

linkcheck_ignore = [
    r".*github\.com.*#",  # javascript based anchors
    r"http://127\.0\.0\.1:.*",  # various examples
    r"https://mybinder\.org/v2/gh/.*",  # lots of 500 errors
    r"https://packages\.ubuntu\.com/search\?keywords=openjdk",  # frequent read timeouts
    r"https://anaconda\.org\/conda-forge",  # frequent read timeouts
]

linkcheck_allowed_redirects = {
    r"https://results\.pre-commit\.ci/latest/github/jupyter/docker-stacks/main": r"https://results\.pre-commit\.ci/run/github/.*",  # Latest main CI build
    r"https://github\.com/jupyter/docker-stacks/issues/new.*": r"https://github\.com/login.*",  # GitHub wants user to be logon to use this features
    r"https://github\.com/orgs/jupyter/teams/docker-image-maintainers/members": r"https://github\.com/login.*",
}


================================================
FILE: docs/contributing/features.md
================================================
# New Features

Thank you for contributing to the Jupyter Docker Stacks!
We review pull requests for new features (e.g., new packages, new scripts, new flags)
to balance the value of the images to the Jupyter community with the cost of maintaining the images over time.

## Suggesting a New Feature

Please follow the process below to suggest a new feature for inclusion in one of the core stacks:

1. Open a [GitHub feature request issue](https://github.com/jupyter/docker-stacks/issues/new?template=feature_request.yml)
   describing the feature you'd like to contribute.
2. Discuss with the maintainers whether the addition makes sense
   in [one of the core stacks](../using/selecting.md#core-stacks),
   as a [way to build a custom set of images](../using/custom-images.md),
   as a [recipe in the documentation](recipes.md),
   as a [community stack](stacks.md),
   or as something else entirely.

## Selection Criteria

Roughly speaking, we evaluate new features based on the following criteria:

- **Usefulness to Jupyter users**:
  Is the feature generally applicable across domains?
  Does it work with JupyterLab, Jupyter Notebook, JupyterHub, etc.?
- **Fit with the image purpose**:
  Does the feature match the theme of the stack to which it will be added?
  Would it fit better in a new community stack?
- **The complexity of build/runtime configuration**:
  How many lines of code does the feature require in one of the Dockerfiles or startup scripts?
  Does it require new scripts entirely?
  Do users need to adjust how they use the images?
- **Impact on image metrics**:
  How many bytes does the feature and its dependencies add to the image(s)?
  How many minutes do they add to the build time?
- **Ability to support the addition**:
  Can existing maintainers answer user questions and address future build issues?
  Are the contributors interested in helping with long-term maintenance?
  Can we write tests to ensure the feature continues to work over the years?

## Submitting a Pull Request

If there's agreement that the feature belongs in one or more of the core stacks:

1. Implement the feature in a local clone of the `jupyter/docker-stacks` project.
2. Please, build the image locally before submitting a pull request.
   It shortens the debugging cycle by taking some load off GitHub Actions,
   which graciously provides free build services for open-source projects like this one.
   If you use `make`, call:

   ```bash
   make build/<somestack>
   ```

3. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request) (PR) with your changes.
4. Watch for GitHub to report a build success or failure for your PR on GitHub.
5. Discuss changes with the maintainers and address any build issues.


================================================
FILE: docs/contributing/issues.md
================================================
# Project Issues

We appreciate you taking the time to report an issue you encountered while using the Jupyter Docker Stacks.
Please review the following guidelines when reporting your problem.

- Please use GitHub's "Report a Vulnerability" button under Security > Advisories on the appropriate repo,
  e.g. [report here for Jupyter Docker Stacks](https://github.com/jupyter/docker-stacks/security/advisories).
  You may also send an email to <mailto:security@ipython.org>, but the GitHub reporting system is preferred.
- If you think your problem is unique to the Jupyter Docker Stacks images,
  please search the [jupyter/docker-stacks issue tracker](https://github.com/jupyter/docker-stacks/issues?q=is%3Aissue%20)
  to see if someone else has already reported the same problem.
  If not, please open a [GitHub bug report issue](https://github.com/jupyter/docker-stacks/issues/new?template=bug_report.yml)
  and provide all the information requested in the issue template.
  Additionally, check the [Troubleshooting Common Problems](../using/troubleshooting.md) page in the documentation before submitting an issue.
- If the issue you're seeing is with one of the open-source libraries included in the Docker images and is reproducible outside the images,
  please file a bug with the appropriate open-source project.
- If you have a general question about how to use the Jupyter Docker Stacks in your environment,
  in conjunction with other tools, customizations, and so on,
  please post your question on the [Jupyter Discourse site](https://discourse.jupyter.org).


================================================
FILE: docs/contributing/lint.md
================================================
# Lint

To enforce some rules, **linters** are used in this project.
Linters can be run either during the **development phase** (by the developer) or the **integration phase** (by GitHub Actions).
To integrate and enforce this process in the project lifecycle, we are using **git hooks** through [pre-commit](https://pre-commit.com/).

## Using pre-commit hooks

### Pre-commit hook installation

_pre-commit_ is a Python package that needs to be installed.
To achieve this, use the generic task to install all Python development dependencies.

```sh
# Install all development dependencies for the project
pip install --upgrade -r requirements-dev.txt
# It can also be installed directly
pip install pre-commit
```

Then the git hooks scripts configured for the project in `.pre-commit-config.yaml` need to be installed in the local git repository.

```sh
pre-commit install
```

### Run

Now, _pre-commit_ (and so configured hooks) will run automatically on `git commit` on each changed file.
However, you can also run it against all files manually.

```{note}
Hadolint pre-commit uses Docker to run, so `docker` should be running while executing this command.
```

```sh
pre-commit run --all-files --hook-stage manual
```

```{note}
We're running `pre-commit` with `--hook-stage manual`, because `pre-commit` is run on modified files only, which doesn't work well with `mypy --follow-imports error`.
More information can be found in [`.pre-commit-config.yaml` file](https://github.com/jupyter/docker-stacks/blob/main/.pre-commit-config.yaml)
```

## Image Lint

To comply with [Docker best practices](https://docs.docker.com/build/building/best-practices/),
we are using the [Hadolint](https://github.com/hadolint/hadolint) tool to analyze each `Dockerfile`.

### Ignoring Rules

Sometimes it is necessary to ignore [some rules](https://github.com/hadolint/hadolint#rules).
The following rules are ignored by default for all images in the `.hadolint.yaml` file.

- [`DL3006`][dl3006]: We use a specific policy to manage image tags.
  - The `docker-stacks-foundation` `FROM` clause is fixed but based on an argument (`ARG`).
  - Building downstream images from (`FROM`) the latest is done on purpose.
- [`DL3008`][dl3008]: System packages are always updated (`apt-get`) to the latest version.
- [`DL3013`][dl3013]: We always install the latest packages using `pip`

The preferred way to ignore other rules is to flag them in the `Dockerfile`.
You can use a special comment directly above the Dockerfile instruction you want to make an exception for.
Ignore rule comments look like `# hadolint ignore=DL3001,SC1081`.
For example:

```dockerfile
FROM ubuntu

# hadolint ignore=DL3003,SC1035
RUN cd /tmp && echo "hello!"
```

[dl3006]: https://github.com/hadolint/hadolint/wiki/DL3006
[dl3008]: https://github.com/hadolint/hadolint/wiki/DL3008
[dl3013]: https://github.com/hadolint/hadolint/wiki/DL3013


================================================
FILE: docs/contributing/packages.md
================================================
# Package Updates

Generally, we do not pin package versions in our `Dockerfile`s.
Dependency resolution is a difficult thing to do.
All this means that packages might have old versions.
Images are rebuilt weekly, so usually, packages receive updates quite frequently.

```{note}
We pin major.minor version of Python, so this will stay the same even after invoking the `mamba update` command.
```

## Outdated packages

To help identify packages that can be updated, you can use the following helper tool.
It will list all the outdated packages installed in the `Dockerfile`.
Dependencies are filtered to display only the requested packages.

```bash
make check-outdated/base-notebook

# INFO     test_outdated:test_outdated.py:80 3/8 (38%) packages could be updated
# INFO     test_outdated:test_outdated.py:82
# Package     Current    Newest
# ----------  ---------  --------
# conda       4.7.12     4.8.2
# jupyterlab  1.2.5      2.0.0
# python      3.7.4      3.8.2
```


================================================
FILE: docs/contributing/recipes.md
================================================
# New Recipes

We welcome contributions of [recipes](../using/recipes.md), which are short examples of using, configuring, or extending the Docker Stacks for inclusion in the documentation site.
Follow the process below to add a new recipe:

1. Open the `docs/using/recipes.md` source file.
2. Add a second-level Markdown heading naming your recipe at the bottom of the file (e.g., `## Slideshows with JupyterLab and RISE`)
3. Write the body of your recipe under the heading, including whatever command line, links, etc. you need.
4. If you have a Dockerfile, please put it in a `recipe_code` subdirectory.
   This file will be built automatically by [contributed-recipes workflow](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/contributed-recipes.yml).
5. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request) (PR) with your changes.
   Maintainers will respond and work with you to address any formatting or content issues.


================================================
FILE: docs/contributing/stacks.md
================================================
# Community Stacks

We love to see the community create and share new Jupyter Docker images.
We've put together a [cookiecutter project](https://github.com/jupyter/cookiecutter-docker-stacks)
and the documentation below to help you get started defining, building, and sharing your Jupyter environments in Docker.

Following these steps will:

1. Set up a project on GitHub containing a Dockerfile based on any image we provide.
2. Configure GitHub Actions to build and test your image when users submit pull requests to your repository.
3. Configure Quay.io to host your images for others to use.
4. Update the [list of community stacks](../using/selecting.md#community-stacks) in this documentation to include your image.

This approach mirrors how we build and share the core stack images.
Feel free to follow it or pave your path using alternative services and build tools.

## Creating a Project

First, install [cookiecutter](https://github.com/cookiecutter/cookiecutter) using _pip_ or _mamba_:

```bash
pip install cookiecutter  # or mamba install cookiecutter
```

Run the cookiecutter command pointing to the [jupyter/cookiecutter-docker-stacks](https://github.com/jupyter/cookiecutter-docker-stacks) project on GitHub.

```bash
cookiecutter https://github.com/jupyter/cookiecutter-docker-stacks.git
```

Enter a name for your new stack image.
This will serve as both the git repository name and the part of the Docker image name after the slash.

```text
stack_name [my-jupyter-stack]:
```

Enter the user or organization name under which this stack will reside on Docker Hub.
You must have access to manage this Docker Hub organization to push images here.

```text
stack_org [my-project]:
```

Select an image from the `jupyter/docker-stacks` project that will serve as the base for your new image.

```text
stack_base_image [quay.io/jupyter/base-notebook]:
```

Enter a longer description of the stack for your README.

```text
stack_description [my-jupyter-stack is a community-maintained Jupyter Docker Stack image]:
```

Create a GitHub repository to store your project.
Initialize your project as a Git repository and push it to GitHub.

```bash
cd <stack_name you chose>

git init
git add .
git commit -m 'Seed repo'
git remote add origin <url from github>
git push -u origin main
```

## Exploring GitHub Actions

1. By default, the newly `.github/workflows/docker.yaml` will trigger the CI pipeline whenever you push to your `main` branch
   and when any Pull Requests are made to your repository.
   For more details on this configuration, visit the [GitHub Actions documentation on triggers](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows).

2. Go to your repository and click on the **Actions** tab.
   From there, you can click on the workflows on the left-hand side of the screen.

   ![GitHub page for jupyter/docker-stacks with the Actions tab active and a rectangle around the "Build Docker Images" workflow in the UI](../_static/contributing/stacks/github-actions-tab.png)

   ```{note}
   The first run is expected to fail because we haven't yet added Docker credentials to push the image
   ```

3. In the next screen, you will see information about the workflow run and duration.
   If you click the button with the workflow name again, you will see the logs for the workflow steps.

   ![GitHub Actions page showing the "Build Docker Images" workflow](../_static/contributing/stacks/github-actions-workflow.png)

## Configuring Docker Hub

```{note}
Jupyter Docker Stacks are hosted on Quay.io, but in this example, we show you how to host your image on Docker Hub.
```

Now, configure Docker Hub to build your stack image and push it to the Docker Hub repository whenever
you merge a GitHub pull request to the main branch of your project.

1. Visit [https://hub.docker.com/](https://hub.docker.com/) and log in.
2. Create a new repository - make sure to use the correct namespace (account or organization).
   Enter the name of the image matching the one you entered when prompted with `stack_name` by the cookiecutter.

   ![Docker Hub - 'Create repository' page with the name field set to "My specialized jupyter stack"](../_static/contributing/stacks/docker-repo-name.png)

3. Enter a description for your image.
4. Click on your avatar in the top-right corner and select Account Settings.

   ![The Docker Hub page zoomed into the user's settings and accounts menu](../_static/contributing/stacks/docker-user-dropdown.png)

5. Click on **Security** and then click on the **New Access Token** button.

   ![Docker Hub - Account page with the "Security" tab active and a rectangle highlighting the "New Access Token" button in the UI](../_static/contributing/stacks/docker-org-security.png)

6. Enter a meaningful name for your token and click on **Generate**

   ![Docker Hub - New Access Token page with the name field set to "test-stack token"](../_static/contributing/stacks/docker-org-create-token.png)

7. Copy the personal access token displayed on the next screen.

   ```{note}
   **You will not be able to see it again after you close the pop-up window**.
   ```

8. Head back to your GitHub repository and click on the **Settings tab**.
9. Click on the **Secrets and variables->Actions** section and then on the **New repository secret** button in the top right corner.

   ![GitHub page with the "Setting" tab active and a rectangle highlighting the "New repository secret" button in the UI](../_static/contributing/stacks/github-create-secrets.png)

10. Create a **DOCKERHUB_TOKEN** secret and paste the Personal Access Token from Docker Hub in the **value** field.

    ![GitHub - Actions/New secret page with the Name field set to "DOCKERHUB_TOKEN"](../_static/contributing/stacks/github-secret-token.png)

11. Now you're ready to go and you can restart a failed workflow.

## Defining Your Image

Make edits to the Dockerfile in your project to add third-party libraries and configure Jupyter applications.
Refer to the Dockerfiles for the core stacks (e.g., [jupyter/datascience-notebook](https://github.com/jupyter/docker-stacks/blob/main/images/datascience-notebook/Dockerfile))
to get a feel for what's possible and the best practices.

[Submit pull requests](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request)
to your project repository on GitHub.
Ensure your image builds correctly on GitHub Actions before merging to the main branch.
After merging to the main branch, your image will be built and pushed to the Docker Hub automatically.

## Sharing Your Image

Finally, if you'd like to add a link to your project to this documentation site, please do the following:

1. Fork the [jupyter/docker-stacks](https://github.com/jupyter/docker-stacks) GitHub repository.
2. Open the `docs/using/selecting.md` source file and locate the **Community Stacks** section in your fork.
3. Add a table entry with a link to your project, a binder link, and a short description of what your Docker image contains.
4. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request) (PR) with your changes.
   Maintainers will respond and work with you to address any formatting or content issues.


================================================
FILE: docs/contributing/tests.md
================================================
# Image Tests

We greatly appreciate Pull Requests that extend the automated tests that vet the basic functionality of the Docker images.

## How the Tests Work

A [GitHub Actions workflow](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-build-test-upload.yml)
runs tests against pull requests submitted to the `jupyter/docker-stacks` repository.

We use the `pytest` module to run tests on the image.
`conftest.py` and `pytest.ini` in the `tests` folder define the environment in which tests are run.
[Read `pytest` documentation](https://docs.pytest.org/en/latest/contents.html).

The actual image-specific test files are located in folders like `tests/by_image/<somestack>/` (e.g., `tests/by_image/docker-stacks-foundation/`, etc.).

```{note}
If your test is located in `tests/by_image/<somestack>/`, it will be run against the `jupyter/<somestack>` image and against all the [images inherited from this image](../using/selecting.md#image-relationships).
```

Many tests make use of global [pytest fixtures](https://docs.pytest.org/en/latest/reference/fixtures.html)
defined in the [conftest.py](https://github.com/jupyter/docker-stacks/blob/main/tests/conftest.py) file.

## Unit tests

You can add a unit test if you want to run a Python script in one of our images.
You should create a `tests/by_image/<somestack>/units/` directory, if it doesn't already exist, and put your file there.
Files in this folder will be executed in the container when tests are run.
You can see an [TensorFlow package example here](https://github.com/jupyter/docker-stacks/blob/HEAD/tests/by_image/tensorflow-notebook/units/unit_tensorflow.py).

## Contributing New Tests

Please follow the process below to add new tests:

1. Add your test code to one of the modules in the `tests/by_image/<somestack>/` directory or create a new module.
2. Build one or more images you intend to test and run the tests locally.
   If you use `make`, call:

   ```bash
   make build/<somestack>
   make test/<somestack>
   ```

3. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request)
   (PR) with your changes.
4. Watch for GitHub to report a build success or failure for your PR on GitHub.
5. Discuss changes with the maintainers and address any issues running the tests on GitHub.


================================================
FILE: docs/index.rst
================================================
.. include:: ../README.md
   :parser: myst_parser.sphinx_

Table of Contents
-----------------

.. toctree::
   :maxdepth: 2
   :caption: User Guide

   using/selecting
   using/running
   using/common
   using/specifics
   using/recipes
   using/custom-images
   using/troubleshooting
   using/faq
   using/changelog

.. toctree::
   :maxdepth: 2
   :caption: Contributor Guide

   contributing/issues
   contributing/features
   contributing/tests
   contributing/lint
   contributing/recipes
   contributing/stacks
   contributing/packages

.. toctree::
   :maxdepth: 2
   :caption: Maintainer Guide

   maintaining/new-images-and-packages-policy
   maintaining/tagging
   maintaining/tasks

.. toctree::
   :maxdepth: 2
   :caption: Getting Help

   Issue Tracker on GitHub <https://github.com/jupyter/docker-stacks/issues>
   Jupyter Discourse Forum <https://discourse.jupyter.org>
   Jupyter Website <https://jupyter.org>


================================================
FILE: docs/maintaining/new-images-and-packages-policy.md
================================================
# New images / packages policy

There are many things we consider while adding new images and packages.

Here is a non-exhaustive list of things we do care about:

1. **Software health**, details, and maintenance status
   - reasonable versioning is adopted, and the version is considered to be stable
   - has been around for several years
   - the package maintains documentation
   - a changelog is actively maintained
   - a release procedure with helpful automation is established
   - multiple people are involved in the maintenance of the project
   - provides a `conda-forge` package besides a `pypi` package, where both are kept up to date
   - supports both `x86_64` and `aarch64` architectures
2. **Installation consequences**
   - GitHub Actions build time
   - Image sizes
   - All requirements should be installed as well
3. Jupyter Docker Stacks _**image fit**_
   - new package or stack is changing (or inherits from) the most suitable stack
4. **Software impact** for users of docker-stacks images
   - How this image can help existing users, or maybe reduce the need to build new images
5. Why it shouldn't just be a documented **recipe**
6. Impact on **security**
   - Does the package open additional ports, or add new web endpoints, that could be exploited?

With all this in mind, we have a voting group, that consists of
[@mathbunnyru](https://github.com/mathbunnyru),
[@consideRatio](https://github.com/consideRatio),
[@yuvipanda](https://github.com/yuvipanda), and
[@manics](https://github.com/manics).

This voting group is responsible for accepting or declining new packages and stacks.
The change is accepted, if there are **at least 2 positive votes**.


================================================
FILE: docs/maintaining/tagging.md
================================================
# Tags and manifests

The main purpose of the source code in [the `tagging` folder](https://github.com/jupyter/docker-stacks/tree/main/tagging) is to
properly write tags file, build history line and manifest for a single-platform image,
apply these tags, and then merge single-platform images into one multi-arch image.

## What is a tag and a manifest

A tag is a label attached to a Docker image identifying specific attributes or versions.
For example, an image `jupyter/base-notebook` with Python 3.10.5 will have a full image name `quay.io/jupyter/base-notebook:python-3.10.5`.
These tags are pushed to our [Quay.io registry](https://quay.io/organization/jupyter).

A manifest is a description of important image attributes written in Markdown format.
For example, we dump all `conda` packages with their versions into the manifest.

## Main principles

- All images are organized in a hierarchical tree.
  More info on [image relationships](../using/selecting.md#image-relationships).
- `TaggerInterface` and `ManifestInterface` are interfaces for functions to generate tags and manifest pieces by running commands in Docker containers.
- Tags and manifests are reevaluated for each image in the hierarchy since values may change between parent and child images.
- To tag an image and create its manifest and build history line, run `make hook/<somestack>` (e.g., `make hook/base-notebook`).

## Utils

### DockerRunner

`DockerRunner` is a helper class to easily run a docker container and execute commands inside this container:

```{literalinclude} tagging_examples/docker_runner.py
:language: py
:lines: 3-
```

### GitHelper

`GitHelper` methods are run in the current `git` repo and give the information about the last commit hash and commit message:

```{literalinclude} tagging_examples/git_helper.py
:language: py
:lines: 3-
```

The prefix of commit hash (namely, 12 letters) is used as an image tag to make it easy to inherit from a fixed version of a docker image.

## Taggers and Manifests

### Tagger

`Tagger` is a function that runs commands inside a docker container to calculate a tag for an image.

All the taggers follow `TaggerInterface`:

```{literalinclude} ../../tagging/taggers/tagger_interface.py
:language: py
:start-at: TaggerInterface
```

So, the `tagger(container)` gets a docker container as an input and returns a tag.

For example:

```{literalinclude} ../../tagging/taggers/sha.py
:language: py
:start-at: def
```

- `taggers/` subdirectory contains all taggers.
- `apps/write_tags_file.py`, `apps/apply_tags.py`, and `apps/merge_tags.py` are Python executables used to write tags for an image, apply tags from a file, and create multi-arch images.

### Manifest

All manifest functions except `build_info_manifest` follow `ManifestInterface`
and `manifest(container)` method returns a piece of the manifest.

```{literalinclude} ../../tagging/manifests/manifest_interface.py
:language: py
:start-at: ManifestInterface
```

For example:

```{literalinclude} ../../tagging/manifests/apt_packages.py
:language: py
:start-at: def
```

where:

- `quoted_output(container, cmd)` simply runs the command inside a container using `DockerRunner.exec_cmd` and wraps it to triple quotes to create a valid markdown piece.
  It also adds the command which was run to the markdown piece.
- `manifests/` subdirectory contains all the manifests.
- `apps/write_manifest.py` is a Python executable to create the build manifest and history line for an image.

## Images Hierarchy

All images' dependencies on each other and what taggers and manifests are applicable to them are defined in `hierarchy/images_hierarchy.py`.

`hierarchy/get_taggers.py` and `hierarchy/get_manifests.py` define functions to get the taggers and manifests for a specific image.


================================================
FILE: docs/maintaining/tagging_examples/docker_runner.py
================================================
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tagging.utils.docker_runner import DockerRunner

with DockerRunner("ubuntu") as container:
    DockerRunner.exec_cmd(container, cmd="env")


================================================
FILE: docs/maintaining/tagging_examples/git_helper.py
================================================
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tagging.utils.git_helper import GitHelper

print("Git hash:", GitHelper.commit_hash())
print("Git message:", GitHelper.commit_message())


================================================
FILE: docs/maintaining/tasks.md
================================================
# Maintainer Playbook

## Merging Pull Requests

To build new images and publish them to the Registry, do the following:

1. Make sure GitHub Actions status checks pass for the PR.
2. Merge the PR.
3. Monitor the merge commit GitHub Actions status.

   ```{note}
   GitHub Actions are pretty reliable, so please investigate if some error occurs.
   Building Docker images in PRs is the same as building them in the default branch.
   The only difference is that single-platform images are pushed to Registry and then tags are merged for `x86_64` and `aarch64`.
   ```

4. Avoid merging another PR to the main branch until all pending builds in the main branch are complete.
   This way, you will know which commit might have broken the build
   and also have the correct tags for moving tags (like the `Python` version).

## Updating Python version

When a new `Python` version is released, we wait for:

- all the dependencies to be available (as wheels or in `conda-forge`).
- the first `Python` patch release for this version.
  This allows us to avoid many bugs, which can happen in a major release.

## Updating the Ubuntu Base Image

`jupyter/docker-stacks-foundation` is based on the LTS Ubuntu docker image.
We wait for the first point release of the new LTS Ubuntu before updating the version.
Other images are directly or indirectly inherited from `jupyter/docker-stacks-foundation`.
We rebuild our images automatically each week, which means they frequently receive updates.

When there's a security fix in the Ubuntu base image, it's a good idea to manually trigger the rebuild of images
[from the GitHub Actions workflow UI](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml).
Pushing the `Run Workflow` button will trigger this process.

## Adding a New Core Image to the Registry

```{note}
In general, we do not add new core images and ask contributors to either
create a [recipe](../using/recipes.md) or [community stack](../contributing/stacks.md).
We have a [policy](./new-images-and-packages-policy.md), which we consider when adding new images or new packages to existing images.
```

[Take a look at an example](https://github.com/jupyter/docker-stacks/pull/1936/files) of adding a new image.

When there's a new stack definition, check before merging the PR:

1. PR includes an update to the stack overview diagram
   [in the documentation](../using/selecting.md#image-relationships).
   The image links to the [blockdiag source](http://interactive.blockdiag.com/) used to create it.
2. PR updates the [Makefile](https://github.com/jupyter/docker-stacks/blob/main/Makefile).
3. Necessary Tagger(s)/Manifest(s) are added for the new image
   in the [tagging](https://github.com/jupyter/docker-stacks/tree/main/tagging) folder.
4. A new repository is created in the `jupyter` organization in the Registry,
   and it's named after the stack folder in the git repo.
5. Robot `Write` permission is added in the `Repository Settings`.

## Adding a New Registry Owner Account

1. Visit <https://quay.io/organization/jupyter/teams/owners>
2. Add the maintainer's username.

## Restarting a failed build

If an automated build in GitHub Actions fails, you can restart the failed jobs on GitHub.
You can also download the artifacts and investigate them for any issues.


================================================
FILE: docs/requirements.txt
================================================
# ReadTheDocs environment contains old package versions preinstalled
# So, to ensure we have modern packages, we pin minimum versions of the packages we need
docutils>=0.17.1
myst-parser>=0.18.0
sphinx>=4.5.0
sphinx-book-theme>=1.0.0
sphinx-copybutton>=0.5.0
sphinx-last-updated-by-git>=0.3.4


================================================
FILE: docs/using/changelog.md
================================================
```{include} ../../CHANGELOG.md

```

<!-- markdownlint-disable-file MD041 -->


================================================
FILE: docs/using/common.md
================================================
# Common Features

Except for `jupyter/docker-stacks-foundation`, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with the JupyterLab frontend.
The container does so by executing a `start-notebook.py` script.
This script configures the internal container environment and then runs `jupyter lab`, passing any command-line arguments received.

This page describes the options supported by the startup script and how to bypass it to run alternative commands.

## Jupyter Server Options

You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html) to the `start-notebook.py` script when launching the container.

1. For example, to secure the Jupyter Server with a [custom password](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#preparing-a-hashed-password)
   hashed using `jupyter_server.auth.passwd()` instead of the default token,
   you can run the following (this hash was generated for the `my-password` password):

   ```bash
   docker run -it --rm -p 8888:8888 quay.io/jupyter/base-notebook \
       start-notebook.py --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do'
   ```

2. To set the [base URL](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#running-the-notebook-with-a-customized-url-prefix) of the Jupyter Server, you can run the following:

   ```bash
   docker run -it --rm -p 8888:8888 quay.io/jupyter/base-notebook \
       start-notebook.py --ServerApp.base_url=/customized/url/prefix/
   ```

## Docker Options

You may instruct the `start-notebook.py` script to customize the container environment before launching the Server.
You do so by passing arguments to the `docker run` command.

### User-related configurations

- `-e NB_USER=<username>` - The desired username and associated home folder.
  The default value is `jovyan`.
  Setting `NB_USER` redefines the `jovyan` default user and ensures that the desired user has the correct file permissions
  for the new home directory created at `/home/<username>`.
  For this option to take effect, you **must** run the container with `--user root`, set the working directory `-w "/home/<username>"`
  and set the environment variable `-e CHOWN_HOME=yes`.

  _Example usage:_

  ```bash
  docker run -it --rm \
      -p 8888:8888 \
      --user root \
      -e NB_USER="my-username" \
      -e CHOWN_HOME=yes \
      -w "/home/my-username" \
      quay.io/jupyter/base-notebook
  ```

  ```{note}
  If you set `NB_USER` to `root`, the `root` home dir will be set to `/home/root`.
  See discussion [here](https://github.com/jupyter/docker-stacks/issues/2042).
  ```

- `-e NB_UID=<numeric uid>` - Instructs the startup script to switch the numeric user ID of `${NB_USER}` to the given value.
  The default value is `1000`.
  This feature is useful when mounting host volumes with specific owner permissions.
  You **must** run the container with `--user root` for this option to take effect.
  (The startup script will `su ${NB_USER}` after adjusting the user ID.)
  Instead, you might consider using the modern Docker-native options [`--user`](https://docs.docker.com/engine/containers/run/#user) and
  [`--group-add`](https://docs.docker.com/engine/containers/run/#additional-groups) - see the last bullet in this section for more details.
  See bullet points regarding `--user` and `--group-add`.

- `-e NB_GID=<numeric gid>` - Instructs the startup script to change the primary group of `${NB_USER}` to `${NB_GID}`
  (the new group is added with a name of `${NB_GROUP}` if it is defined. Otherwise, the group is named `${NB_USER}`).
  This feature is useful when mounting host volumes with specific group permissions.
  You **must** run the container with `--user root` for this option to take effect.
  (The startup script will `su ${NB_USER}` after adjusting the group ID.)
  Instead, you might consider using modern Docker options `--user` and `--group-add`.
  See bullet points regarding `--user` and `--group-add`.
  The user is added to the supplemental group `users` (gid 100) to grant write access to the home directory and `/opt/conda`.
  If you override the user/group logic, ensure the user stays in the group `users` if you want them to be able to modify files in the image.

- `-e NB_GROUP=<name>` - The name used for `${NB_GID}`, which defaults to `${NB_USER}`.
  This group name is only used if `${NB_GID}` is specified and completely optional: there is only a cosmetic effect.

- `--user 5000 --group-add users` - Launches the container with a specific user ID and adds that user to the `users` group so that it can modify files in the default home directory and `/opt/conda`.
  You can use these arguments as alternatives to setting `${NB_UID}` and `${NB_GID}`.

## Permission-specific configurations

- `-e NB_UMASK=<umask>` - Configures Jupyter to use a different `umask` value from default, i.e. `022`.
  For example, if setting `umask` to `002`, new files will be readable and writable by group members instead of the owner only.
  [Check this Wikipedia article](https://en.wikipedia.org/wiki/Umask) for an in-depth description of `umask` and suitable values for multiple needs.
  While the default `umask` value should be sufficient for most use cases, you can set the `NB_UMASK` value to fit your requirements.

  ```{note}
  When `NB_UMASK` is set, it only applies to the Jupyter process itself -
  you cannot use it to set a `umask` for additional files created during `run-hooks.sh`.
  For example, via `pip` or `conda`.
  If you need to set a `umask` for these, you **must** set the `umask` value for each command.
  ```

- `-e CHOWN_HOME=yes` - Instructs the startup script to change the `${NB_USER}` home directory owner and group to the current value of `${NB_UID}` and `${NB_GID}`.
  This change will take effect even if the user home directory is mounted from the host using `-v` as described below.
  The change is **not** applied recursively by default.
  You can modify the `chown` behavior by setting `CHOWN_HOME_OPTS` (e.g., `-e CHOWN_HOME_OPTS='-R'`).

- `-e CHOWN_EXTRA="<some dir>,<some other dir>"` - Instructs the startup script to change the owner and group of each comma-separated container directory to the current value of `${NB_UID}` and `${NB_GID}`.
  The change is **not** applied recursively by default.
  You can modify the `chown` behavior by setting `CHOWN_EXTRA_OPTS` (e.g., `-e CHOWN_EXTRA_OPTS='-R'`).

- `-e GRANT_SUDO=yes` - Instructs the startup script to grant the `NB_USER` user passwordless `sudo` capability.
  You do **not** need this option to allow the user to `conda` or `pip` install additional packages.
  This option is helpful for cases when you wish to give `${NB_USER}` the ability to install OS packages with `apt` or modify other root-owned files in the container.
  You **must** run the container with `--user root` for this option to take effect.
  (The `start-notebook.py` script will `su ${NB_USER}` after adding `${NB_USER}` to sudoers.)
  **You should only enable `sudo` if you trust the user or if the container runs on an isolated host.**

### Additional runtime configurations

- `-e GEN_CERT=yes` - Instructs the startup script to generate a self-signed SSL certificate.
  Configures Jupyter Server to use it to accept encrypted HTTPS connections.
- `-e DOCKER_STACKS_JUPYTER_CMD=<jupyter command>` - Instructs the startup script to run `jupyter ${DOCKER_STACKS_JUPYTER_CMD}` instead of the default `jupyter lab` command.
  See [Switching back to the classic notebook or using a different startup command][switch_back] for available options.
  This setting is helpful in container orchestration environments where setting environment variables is more straightforward than changing command line parameters.
- `-e RESTARTABLE=yes` - Runs Jupyter in a loop so that quitting Jupyter does not cause the container to exit.
  This may be useful when installing extensions that require restarting Jupyter.
- `-v /some/host/folder/for/work:/home/jovyan/work` - Mounts a host machine directory as a folder in the container.
  This configuration is useful for preserving notebooks and other work even after the container has been destroyed.
  **You must grant the within-container notebook user or group (`NB_UID` or `NB_GID`) write access to the host directory (e.g., `sudo chown 1000 /some/host/folder/for/work`).**
- `-e JUPYTER_ENV_VARS_TO_UNSET=ADMIN_SECRET_1,ADMIN_SECRET_2` - Unsets specified environment variables in the default startup script.
  The variables are unset after the hooks have been executed but before the command provided to the startup script runs.
- `-e NOTEBOOK_ARGS="--log-level='DEBUG' --dev-mode"` - Adds custom options to add to `jupyter` commands.
  This way, the user could use any option supported by the `jupyter` subcommand.
- `-e JUPYTER_PORT=8117` - Changes the port in the container that Jupyter is using to the value of the `${JUPYTER_PORT}` environment variable.
  This may be useful if you run multiple instances of Jupyter in swarm mode and want to use a different port for each instance.

## Startup Hooks

You can further customize the container environment by adding shell scripts (`*.sh`) to be sourced
or executables (`chmod +x`) to be run to the paths below:

- `/usr/local/bin/start-notebook.d/` - handled **before** any of the standard options noted above is applied
- `/usr/local/bin/before-notebook.d/` - handled **after** all the standard options noted above are applied
  and ran right before the Server launches

[Open the `run-hooks.sh` script](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/run-hooks.sh) and how it's used in the [`start.sh`](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/start.sh)
script for execution details.

## SSL Certificates

You may mount an SSL key and certificate file into a container and configure the Jupyter Server to use them to accept HTTPS connections.
For example, to mount a host folder containing a `notebook.key` and `notebook.crt` and use them, you might run the following:

```bash
docker run -it --rm -p 8888:8888 \
    -v /some/host/folder:/etc/ssl/notebook \
    quay.io/jupyter/base-notebook \
    start-notebook.py \
    --ServerApp.keyfile=/etc/ssl/notebook/notebook.key \
    --ServerApp.certfile=/etc/ssl/notebook/notebook.crt
```

Alternatively, you may mount a single PEM file containing both the key and certificate.
For example:

```bash
docker run -it --rm -p 8888:8888 \
    -v /some/host/folder/notebook.pem:/etc/ssl/notebook.pem \
    quay.io/jupyter/base-notebook \
    start-notebook.py \
    --ServerApp.certfile=/etc/ssl/notebook.pem
```

In either case, Jupyter Server expects the key and certificate to be a **base64 encoded text file**.
The certificate file or PEM may contain one or more certificates (e.g., server, intermediate, and root).

For additional information about using SSL, see the following:

- The [docker-stacks/examples](https://github.com/jupyter/docker-stacks/tree/main/examples)
  for information about how to use
  [Let's Encrypt](https://letsencrypt.org/) certificates when you run these stacks on a publicly visible domain.
- The [`jupyter_server_config.py`](https://github.com/jupyter/docker-stacks/blob/main/images/base-notebook/jupyter_server_config.py)
  file for how this Docker image generates a self-signed certificate.
- The [Jupyter Server documentation](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#securing-a-jupyter-server)
  for best practices about securing a public Server in general.

## Alternative Commands

### Switching back to the classic notebook or using a different startup command

JupyterLab, built on top of Jupyter Server, is now the default for all the images of the stack.
However, switching back to the classic notebook or using a different startup command is still possible.
You can achieve this by setting the environment variable `DOCKER_STACKS_JUPYTER_CMD` at container startup.
The table below shows some options.
Since `Jupyter Notebook v7` `jupyter-server` is used as a backend.

| `DOCKER_STACKS_JUPYTER_CMD` | Frontend         |
| --------------------------- | ---------------- |
| `lab` (default)             | JupyterLab       |
| `notebook`                  | Jupyter Notebook |
| `nbclassic`                 | NbClassic        |
| `server`                    | None             |
| `retro`\*                   | RetroLab         |

```{note}
- Changing frontend for **JupyterHub singleuser image** is described in [JupyterHub docs](https://jupyterhub.readthedocs.io/en/latest/howto/configuration/config-user-env.html#switching-back-to-the-classic-notebook).
- \* `retro` is not installed at this time, but it could be the case in the future or in a community stack.
- Any other valid `jupyter` subcommand that starts the Jupyter Application can be used.
```

Example:

```bash
# Run Jupyter Server with the Jupyter Notebook frontend
docker run -it --rm \
    -p 8888:8888 \
    -e DOCKER_STACKS_JUPYTER_CMD=notebook \
    quay.io/jupyter/base-notebook

# Executing the command: start-notebook.py
# Executing: jupyter notebook
# ...

# Use Jupyter NBClassic frontend
docker run -it --rm \
    -p 8888:8888 \
    -e DOCKER_STACKS_JUPYTER_CMD=nbclassic \
    quay.io/jupyter/base-notebook

# Executing the command: start-notebook.py
# Executing: jupyter nbclassic
# ...
```

### `start.sh`

Most of the configuration options in the `start-notebook.py` script are handled by an internal `start.sh` script that automatically runs before the command provided to the container
(it's set as the container entrypoint).
This allows you to specify an arbitrary command that takes advantage of all these features.
For example, to run the text-based `ipython` console in a container, do the following:

```bash
docker run -it --rm quay.io/jupyter/base-notebook ipython
```

This script is handy when you derive a new Dockerfile from this image and install additional Jupyter applications with subcommands like `jupyter console`, `jupyter kernelgateway`, etc.

## Conda Environments

The default Python 3.x [Conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) resides in `/opt/conda`.
The `/opt/conda/bin` directory is part of the default `jovyan` user's `${PATH}`.
That directory is also searched for binaries when run using `sudo` (`sudo my_binary` will search for `my_binary` in `/opt/conda/bin/`).

The `jovyan` user has full read/write access to the `/opt/conda` directory.
You can use either `mamba`, `pip`, or `conda` (`mamba` is recommended) to install new packages without any additional permissions.

```bash
# install a package into the default (python 3.x) environment and cleanup it after
# the installation
mamba install --yes some-package && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

pip install --no-cache-dir some-package && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

conda install --yes some-package && \
    conda clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"
```

### Using Alternative Channels

Conda is configured by default to use only the [`conda-forge`](https://anaconda.org/conda-forge) channel.
However, you can use alternative channels, either one-shot by overwriting the default channel in the installation command or by configuring `mamba` to use different channels.
The examples below show how to use the [anaconda default channels](https://repo.anaconda.com/pkgs/main) instead of `conda-forge` to install packages.

```bash
# using defaults channels to install a package
mamba install --channel defaults humanize

# configure conda to add default channels at the top of the list
conda config --system --prepend channels defaults

# install a package
mamba install --yes humanize && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"
```

[switch_back]: #switching-back-to-the-classic-notebook-or-using-a-different-startup-command


================================================
FILE: docs/using/custom-images.md
================================================
# Building a custom set of images

This section describes how to build a custom set of images.
It may be helpful if you need to change the Ubuntu or Python version, or to make a significant change to the build process itself.

This project only builds one set of images at a time.
If you want to use older images, [take a look here](../index.rst/#using-old-images).

## Automating your build using template cookiecutter project

If you wish to build your own image on top of one of our images and automate your build process,
please, [take a look at cookiecutter template](../contributing/stacks.md).

## Custom arguments

Our repository provides several customization points:

- `ROOT_IMAGE` (docker argument) - the parent image for `docker-stacks-foundation` image
- `PYTHON_VERSION` (docker argument) - the Python version to install in `docker-stacks-foundation` image
- `REGISTRY`, `OWNER`, `BASE_IMAGE` (docker arguments) - they allow to specify parent image for all the other images
- `REGISTRY`, `OWNER` (part of `env` in some GitHub workflows) - these allow to properly tag and refer to images during following steps:
  - [`build-test-upload`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-build-test-upload.yml)
  - [`contributed-recipes`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/contributed-recipes.yml)
  - [`tag-push-merge`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-tag-push-merge.yml)

These customization points can't be changed during runtime.
Read more about [Docker build arguments](https://docs.docker.com/build/building/variables/#arg-usage-example) and [GitHub environment variables for a single workflow](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#defining-environment-variables-for-a-single-workflow).

## Building stack images with custom arguments

A selection of prebuilt images are available from [Quay.io](https://quay.io/organization/jupyter),
however, it's impossible to cater to everybody's needs.
For extensive customization with an automated build pipeline,
you may wish to create a [community-maintained stack](../contributing/stacks),
however, for minor customizations, this may be overkill.
For example, you may wish to use the same Jupyter stacks but built on a different base image,
or built with a different Python version.

To achieve this you can use [Docker Bake](https://docs.docker.com/build/bake/)
to build the stacks locally with custom arguments.

```{note}
Custom arguments may result in build errors due to incompatibility.
If so your use-case may require a fully customized stack.
```

As a basic example, if you want to build a custom image based on the `minimal-notebook` image using `Python 3.12`,
then with a Dockerfile like:

```{code-block} Dockerfile
:caption: Dockerfile

ARG BASE_IMAGE=minimal-notebook
FROM $BASE_IMAGE
...
```

Include the file below in your project:

```{literalinclude} recipe_code/docker-bake.custom-python.hcl
:force:
:language: hcl
:caption: docker-bake.hcl
```

To build this stack, in the same directory run:

```bash
docker buildx bake
```

Docker Bake then determines the correct build order from the `contexts` parameters
and builds the stack as requested.

This image can then be run the same way as any other image provided by this project, for example:

```bash
docker run -it --rm -p 8888:8888 custom-jupyter
```

or referenced in a Docker Compose file.

## Forking our repository

If for some reason, you need to change more things in our images, feel free to fork it and change it any way you want.
If your customization is easy to backport to the main repo and might be helpful for other users, feel free to create a PR.

It is almost always a great idea to keep your diff as small as possible and to merge/rebase the latest version of our repo in your project.


================================================
FILE: docs/using/faq.md
================================================
# Frequently Asked Questions (FAQ)

## How to persist user data

There are two types of data you might want to persist.

1. If you want to persist your environment (i.e. packages installed by `mamba`, `conda`, `pip`, `apt-get`, and so on),
   then you should create an inherited image and install packages only once while building your Dockerfile.
   Take a look at [an example of using `mamba` and `pip`](./recipes.md#using-mamba-install-recommended-or-pip-install-in-a-child-docker-image) in a child image.

   ```{note}
   If you install a package inside a running container (for example you run `pip install <package>` in a terminal),
   it won't be preserved when you next run your image.
   To make it work, install this package in your inherited image and rerun the `docker build` command.
   ```

2. If you want to persist user data (files created by you, like `Python` scripts, notebooks, text files, and so on),
   then you should use a
   [Docker bind mount](https://docs.docker.com/engine/storage/bind-mounts/) or
   [Docker Volume](https://docs.docker.com/engine/storage/volumes/).
   You can find [an example of using a bind mount here](./running.md#example-2).
   There is also [a mount troubleshooting section](./troubleshooting.md#permission-denied-when-mounting-volumes) if you experience any issues.

## Why we don't add your favorite package

We have lots of users with different packages they want to use.
Adding them all is impossible, so we have several images to choose from.
[Choose the image](selecting.md) that is closest to your needs, and feel free to [add your package on top of our images](recipes.md#using-mamba-install-recommended-or-pip-install-in-a-child-docker-image).

## Who is `jovyan`

As described [in this issue comment](https://github.com/jupyter/docker-stacks/issues/358#issuecomment-288844834):

```text
Jo·vy·an
/ˈjōvēən/
noun – an inhabitant of Jupyter
```

`Jovyan` is often a special term used to describe members of the Jupyter community.
It is also used as the user ID in the Jupyter Docker stacks or referenced in conversations.
You can find more information on [the Jupyter Community documentation](https://docs.jupyter.org/en/latest/community/content-community.html#what-is-a-jovyan).

## How to give root permissions to the user

We have a [recipe for enabling root permissions](recipes.md#using-sudo-within-a-container).


================================================
FILE: docs/using/recipe_code/custom_environment.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/minimal-notebook
FROM $BASE_IMAGE

# Name your environment and choose the Python version
ARG env_name=python313
ARG py_ver=3.13

# You can add additional libraries here
RUN mamba create --yes -p "${CONDA_DIR}/envs/${env_name}" \
    python=${py_ver} \
    'ipykernel' \
    'jupyterlab' && \
    mamba clean --all -f -y

# Alternatively, you can comment out the lines above and uncomment those below
# if you'd prefer to use a YAML file present in the docker build context

# COPY --chown=${NB_UID}:${NB_GID} environment.yml /tmp/
# RUN mamba env create -p "${CONDA_DIR}/envs/${env_name}" -f /tmp/environment.yml && \
#     mamba clean --all -f -y

# Create Python kernel and link it to jupyter
RUN "${CONDA_DIR}/envs/${env_name}/bin/python" -m ipykernel install --user --name="${env_name}" && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

# Any additional `pip` installs can be added by using the following line
# Using `mamba` is highly recommended though
RUN "${CONDA_DIR}/envs/${env_name}/bin/pip" install --no-cache-dir \
    'flake8'

# This changes the custom Python kernel so that the custom environment will
# be activated for the respective Jupyter Notebook and Jupyter Console
# hadolint ignore=DL3059
RUN /opt/setup-scripts/activate_notebook_custom_env.py "${env_name}"

# Comment the line above and uncomment the section below instead to activate the custom environment by default
# Note: uncommenting this section makes "${env_name}" default both for Jupyter Notebook and Terminals
# More information here: https://github.com/jupyter/docker-stacks/pull/2047
# USER root
# RUN \
#     # This changes a startup hook, which will activate the custom environment for the process
#     echo conda activate "${env_name}" >> /usr/local/bin/before-notebook.d/10activate-conda-env.sh && \
#     # This makes the custom environment default in Jupyter Terminals for all users which might be created later
#     echo conda activate "${env_name}" >> /etc/skel/.bashrc && \
#     # This makes the custom environment default in Jupyter Terminals for already existing NB_USER
#     echo conda activate "${env_name}" >> "/home/${NB_USER}/.bashrc"

USER ${NB_UID}


================================================
FILE: docs/using/recipe_code/dask_jupyterlab.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

# Install the Dask dashboard
RUN mamba install --yes 'dask-labextension' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

# Dask Scheduler port
EXPOSE 8787


================================================
FILE: docs/using/recipe_code/docker-bake.custom-python.hcl
================================================
group "default" {
    targets = ["custom-notebook"]
}

target "foundation" {
    context = "https://github.com/jupyter/docker-stacks.git#main:images/docker-stacks-foundation"
    args = {
        PYTHON_VERSION = "3.13"
    }
    tags = ["docker-stacks-foundation"]
}

target "base-notebook" {
    context = "https://github.com/jupyter/docker-stacks.git#main:images/base-notebook"
    contexts = {
        docker-stacks-foundation = "target:foundation"
    }
    args = {
        BASE_IMAGE = "docker-stacks-foundation"
    }
    tags = ["base-notebook"]
}

target "minimal-notebook" {
    context = "https://github.com/jupyter/docker-stacks.git#main:images/minimal-notebook"
    contexts = {
        base-notebook = "target:base-notebook"
    }
    args = {
        BASE_IMAGE = "base-notebook"
    }
    tags = ["minimal-notebook"]
}

target "custom-notebook" {
    context = "."
    contexts = {
        minimal-notebook = "target:minimal-notebook"
    }
    args = {
        BASE_IMAGE = "minimal-notebook"
    }
    tags = ["custom-jupyter"]
}


================================================
FILE: docs/using/recipe_code/generate_matrix.py
================================================
#!/usr/bin/env python3
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import json
from pathlib import Path

THIS_DIR = Path(__file__).parent.resolve()

RUNS_ON = ["ubuntu-24.04", "ubuntu-24.04-arm"]
ARM_INCOMPATIBLE_IMAGES = {"oracledb.dockerfile"}
BASE_IMAGE_PREFIX = "ARG BASE_IMAGE="


def extract_base_image(dockerfile: Path) -> str:
    """Extract base image from dockerfile"""
    for line in dockerfile.read_text().splitlines():
        if line.startswith(BASE_IMAGE_PREFIX):
            full_image = line[len(BASE_IMAGE_PREFIX) :]
            image_name = full_image[full_image.rfind("/") + 1 :]
            return "" if ":" in image_name else image_name
    raise RuntimeError(f"Base image not found in {dockerfile}")


def get_platform(runs_on: str) -> str:
    """Get platform architecture based on runner"""
    return "x86_64" if runs_on == "ubuntu-24.04" else "aarch64"


def generate_matrix() -> dict[str, list[dict[str, str]]]:
    """Generate build matrix for GitHub Actions"""
    dockerfiles = sorted(THIS_DIR.glob("*.dockerfile"))
    configurations: list[dict[str, str]] = []

    for dockerfile in dockerfiles:
        dockerfile_name = dockerfile.name

        for run in RUNS_ON:
            # Skip ARM builds for incompatible images
            if dockerfile_name in ARM_INCOMPATIBLE_IMAGES and run == "ubuntu-24.04-arm":
                continue

            configurations.append(
                {
                    "dockerfile": dockerfile_name,
                    "runs-on": run,
                    "platform": get_platform(run),
                    "parent-image": extract_base_image(dockerfile),
                }
            )

    return {"include": configurations}


if __name__ == "__main__":
    print(f"matrix={json.dumps(generate_matrix())}")


================================================
FILE: docs/using/recipe_code/ijavascript.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

USER root

RUN apt-get update --yes && \
    apt-get install --yes --no-install-recommends \
    make \
    g++ && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

USER ${NB_UID}

# NodeJS <= 20 is required
# https://github.com/n-riesco/ijavascript/issues/184
RUN mamba install --yes nodejs=20.* && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

# hadolint ignore=DL3016
RUN npm install -g ijavascript
# hadolint ignore=DL3059
RUN ijsinstall


================================================
FILE: docs/using/recipe_code/jupyterhub_version.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

RUN mamba install --yes 'jupyterhub-singleuser==5.2.1' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/mamba_install.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

RUN mamba install --yes 'flake8' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

# Install from the requirements.txt file
COPY --chown=${NB_UID}:${NB_GID} requirements.txt /tmp/
RUN mamba install --yes --file /tmp/requirements.txt && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/manpage_install.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

USER root

# `/etc/dpkg/dpkg.cfg.d/excludes` contains several `path-exclude`s, including man pages
# Remove it, then install man, install docs
RUN rm /etc/dpkg/dpkg.cfg.d/excludes && \
    apt-get update --yes && \
    dpkg -l | grep ^ii | cut -d' ' -f3 | xargs apt-get install --yes --no-install-recommends --reinstall man && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

USER ${NB_UID}


================================================
FILE: docs/using/recipe_code/microsoft_odbc.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

USER root

ENV MSSQL_DRIVER="ODBC Driver 18 for SQL Server"
ENV PATH="/opt/mssql-tools18/bin:${PATH}"

RUN apt-get update --yes && \
    apt-get install --yes --no-install-recommends curl gnupg2 lsb-release && \
    curl -fsSL "https://packages.microsoft.com/keys/microsoft.asc" | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg && \
    curl "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list" > /etc/apt/sources.list.d/mssql-release.list && \
    apt-get update --yes && \
    ACCEPT_EULA=Y apt-get install --yes --no-install-recommends msodbcsql18 && \
    # optional: for bcp and sqlcmd
    ACCEPT_EULA=Y apt-get install --yes --no-install-recommends mssql-tools18 && \
    # optional: for unixODBC development headers
    apt-get install --yes --no-install-recommends unixodbc-dev && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Switch back to jovyan to avoid accidental container runs as root
USER ${NB_UID}

RUN mamba install --yes 'pyodbc' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/oracledb.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

USER root

# Install Java & Oracle SQL Instant Client
RUN apt-get update --yes && \
    apt-get install --yes --no-install-recommends software-properties-common && \
    add-apt-repository universe && \
    apt-get update --yes && \
    apt-get install --yes --no-install-recommends alien default-jre default-jdk openjdk-11-jdk libaio1t64 && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Oracle
ARG INSTANTCLIENT_MAJOR_VERSION=23
ARG INSTANTCLIENT_BIN_SUFFIX=${INSTANTCLIENT_MAJOR_VERSION}.6.0.24.10-1.el9.x86_64.rpm
ARG INSTANTCLIENT_URL=https://download.oracle.com/otn_software/linux/instantclient/2360000

# Then install Oracle SQL Instant client, SQL+Plus, tools, and JDBC.
# Note: You may need to change the URL to a newer version.
# See: https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html
RUN mkdir "/opt/oracle"
WORKDIR "/opt/oracle"
# alien doesn't work well with sqlplus, so skipping it for now
RUN wget --progress=dot:giga "${INSTANTCLIENT_URL}/oracle-instantclient-basiclite-${INSTANTCLIENT_BIN_SUFFIX}" && \
    alien --install --scripts "oracle-instantclient-basiclite-${INSTANTCLIENT_BIN_SUFFIX}" && \
    wget --progress=dot:giga "${INSTANTCLIENT_URL}/oracle-instantclient-sqlplus-${INSTANTCLIENT_BIN_SUFFIX}" && \
    # alien --install --scripts "oracle-instantclient-sqlplus-${INSTANTCLIENT_BIN_SUFFIX}" && \
    wget --progress=dot:giga "${INSTANTCLIENT_URL}/oracle-instantclient-tools-${INSTANTCLIENT_BIN_SUFFIX}" && \
    alien --install --scripts "oracle-instantclient-tools-${INSTANTCLIENT_BIN_SUFFIX}" && \
    wget --progress=dot:giga "${INSTANTCLIENT_URL}/oracle-instantclient-jdbc-${INSTANTCLIENT_BIN_SUFFIX}" && \
    alien --install --scripts "oracle-instantclient-jdbc-${INSTANTCLIENT_BIN_SUFFIX}" && \
    chown -R "${NB_UID}":"${NB_GID}" "${HOME}/.rpmdb" && \
    rm -f ./*.rpm

# And configure variables
RUN echo "ORACLE_HOME=/usr/lib/oracle/${INSTANTCLIENT_MAJOR_VERSION}/client64" >> "${HOME}/.bashrc" && \
    echo "PATH=\"${ORACLE_HOME}/bin:${PATH}\"" >> "${HOME}/.bashrc" && \
    echo "LD_LIBRARY_PATH=\"${ORACLE_HOME}/lib:${LD_LIBRARY_PATH}\"" >> "${HOME}/.bashrc" && \
    echo "export ORACLE_HOME" >> "${HOME}/.bashrc" && \
    echo "export PATH" >> "${HOME}/.bashrc" && \
    echo "export LD_LIBRARY_PATH" >> "${HOME}/.bashrc"

# Add credentials for /redacted/ using Oracle DB.
WORKDIR /usr/lib/oracle/${INSTANTCLIENT_MAJOR_VERSION}/client64/lib/network/admin/
# Add a wildcard `[]` on the last letter of the filename to avoid throwing an error if the file does not exist.
# See: https://stackoverflow.com/questions/31528384/conditional-copy-add-in-dockerfile
COPY cwallet.ss[o] ./
COPY sqlnet.or[a] ./
COPY tnsnames.or[a] ./

# Switch back to jovyan to avoid accidental container runs as root
USER "${NB_UID}"

WORKDIR "${HOME}"

# Install `oracledb` Python library to use Oracle SQL Instant Client
RUN mamba install --yes 'oracledb' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/pip_install.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

# Install in the default python3 environment
RUN pip install --no-cache-dir 'flake8' && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

# Install from the requirements.txt file
COPY --chown=${NB_UID}:${NB_GID} requirements.txt /tmp/
RUN pip install --no-cache-dir --requirement /tmp/requirements.txt && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/requirements.txt
================================================
autoflake


================================================
FILE: docs/using/recipe_code/rise_jupyterlab.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

RUN mamba install --yes 'jupyterlab_rise' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/spellcheck_notebook_v6.dockerfile
================================================
# Using Docker Hub here, because this image is old and not pushed to Quay.io
ARG BASE_IMAGE=docker.io/jupyter/base-notebook:notebook-6.5.4
FROM $BASE_IMAGE

RUN pip install --no-cache-dir 'jupyter_contrib_nbextensions' && \
    jupyter contrib nbextension install --user && \
    # can modify or enable additional extensions here
    jupyter nbclassic-extension enable spellchecker/main --user && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipe_code/xgboost.dockerfile
================================================
ARG BASE_IMAGE=quay.io/jupyter/base-notebook
FROM $BASE_IMAGE

RUN mamba install --yes 'py-xgboost' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"


================================================
FILE: docs/using/recipes.md
================================================
# Contributed Recipes

Users sometimes share interesting ways of using the Jupyter Docker Stacks.
We encourage users to [contribute these recipes](../contributing/recipes.md) to the documentation in case they prove helpful to other community members by submitting a pull request to `docs/using/recipes.md`.
The sections below capture this knowledge.

All the recipes here assume you would like to use an image built by this project and install some things on top of it.
If you would like to build a custom set of images, [take a look at the docs](custom-images.md).

## Using `sudo` within a container

Password authentication is disabled for the `NB_USER` (e.g., `jovyan`).
We made this choice to avoid distributing images with a weak default password that users ~might~ will forget to change before running a container on a publicly accessible host.

You can grant the within-container `NB_USER` passwordless `sudo` access by adding `--user root` and `-e GRANT_SUDO=yes` to your Docker command line or appropriate container orchestrator config.

For example:

```bash
docker run -it --rm \
    --user root \
    -e GRANT_SUDO=yes \
    quay.io/jupyter/base-notebook
```

**You should only enable `sudo` if you trust the user and/or if the container is running on an isolated host.**
See [Docker security documentation](https://docs.docker.com/engine/security/userns-remap/) for more information about running containers as `root`.

## Using `mamba install` (recommended) or `pip install` in a Child Docker image

Create a new Dockerfile like the one shown below.
To use a requirements.txt file, first, create your `requirements.txt` file with the listing of packages desired.

```{literalinclude} recipe_code/mamba_install.dockerfile
:language: docker
```

`pip` usage is similar:

```{literalinclude} recipe_code/pip_install.dockerfile
:language: docker
```

Then build a new image.

```bash
docker build --rm --tag my-custom-image .
```

You can then run the image as follows:

```bash
docker run -it --rm \
    -p 8888:8888 \
    my-custom-image
```

## Add a custom conda environment and Jupyter kernel

The default version of `Python` that ships with the image may not be the version you want.
The instructions below permit adding a conda environment with a different `Python` version and making it accessible to Jupyter.
You may also use older images like `jupyter/base-notebook:python-3.10`.
We also maintain a [full build history](https://github.com/jupyter/docker-stacks/wiki).

```{literalinclude} recipe_code/custom_environment.dockerfile
:language: docker
```

## Dask JupyterLab Extension

[Dask JupyterLab Extension](https://github.com/dask/dask-labextension) provides a JupyterLab extension to manage Dask clusters, as well as embed Dask's dashboard plots directly into JupyterLab panes.
Create the Dockerfile as:

```{literalinclude} recipe_code/dask_jupyterlab.dockerfile
:language: docker
```

And build the image as:

```bash
docker build --rm --tag my-custom-image .
```

Once built, run using the command:

```bash
docker run -it --rm \
    -p 8888:8888 \
    -p 8787:8787 \
    my-custom-image
```

## Let's Encrypt a Server

```{warning}
This recipe is not tested and might be broken.
```

See the README for basic automation here
<https://github.com/jupyter/docker-stacks/tree/main/examples/make-deploy>
which includes steps for requesting and renewing a Let's Encrypt certificate.

Ref: <https://github.com/jupyter/docker-stacks/issues/78>

## Slideshows with JupyterLab and RISE

[RISE](https://github.com/jupyterlab-contrib/rise): "Live" Reveal.js JupyterLab Slideshow Extension.

```{note}
We're providing the recipe to install the JupyterLab extension.
You can find the original Jupyter Notebook extension [here](https://github.com/damianavila/RISE)
```

```{literalinclude} recipe_code/rise_jupyterlab.dockerfile
:language: docker
```

## xgboost

```{literalinclude} recipe_code/xgboost.dockerfile
:language: docker
```

## Running behind an nginx proxy

```{warning}
This recipe is not tested and might be broken.
```

Sometimes it is helpful to run the Jupyter instance behind an nginx proxy, for example:

- you would prefer to access the notebook at a server URL with a path
  (`https://example.com/jupyter`) rather than a port (`https://example.com:8888`)
- you may have many services in addition to Jupyter running on the same server
  and want nginx to help improve server performance in managing the connections

Here is a [quick example of NGINX configuration](https://gist.github.com/cboettig/8643341bd3c93b62b5c2) to get started.
You'll need a server, a `.crt`, and a `.key` file for your server, and `docker` & `docker-compose` installed.
Then download the files at that gist and run `docker-compose up` to test it out.
Customize the `nginx.conf` file to set the desired paths and add other services.

## Host volume mounts and notebook errors

If you are mounting a host directory as `/home/jovyan/work` in your container,
and you receive permission errors or connection errors when you create a notebook,
be sure that the `jovyan` user (`UID=1000` by default) has read/write access to the directory on the host.
Alternatively, specify the UID of the `jovyan` user on container startup using the `-e NB_UID` option
described in the [Common Features, Docker Options section](common.md#docker-options)

Ref: <https://github.com/jupyter/docker-stacks/issues/199>

## Manpage installation

Most images, including our Ubuntu base image, ship without manpages installed to save space.
You can use the following Dockerfile to inherit from one of our images to enable manpages:

```{literalinclude} recipe_code/manpage_install.dockerfile
:language: docker
```

Adding the documentation on top of the existing image wastes a lot of space
and requires reinstalling every system package,
which can take additional time and bandwidth.
Enabling manpages in the base Ubuntu layer prevents this image bloat.
To achieve this, use the previous `Dockerfile`'s commands with the original `ubuntu` image as your base image:

```dockerfile
FROM ubuntu:24.04
```

Be sure to check the current base image in `jupyter/docker-stacks-foundation` before building.

## JupyterHub

We also have contributed recipes for using JupyterHub.

### Use JupyterHub's DockerSpawner

You can find [an example of using DockerSpawner](https://github.com/jupyterhub/jupyterhub-deploy-docker/tree/main/basic-example).

### Containers with a specific version of JupyterHub

The version of `jupyterhub` in your image should match the
version in JupyterHub itself.
To use a specific version of JupyterHub, do the following:

```{literalinclude} recipe_code/jupyterhub_version.dockerfile
:language: docker
```

## Spark

A few suggestions have been made regarding using Docker Stacks with Spark.

### Using PySpark with AWS S3

```{warning}
This recipe is not tested and might be broken.
```

Using Spark session for Hadoop 2.7.3

```python
import os

# To figure out what version of Hadoop, run:
# ls /usr/local/spark/jars/hadoop*
os.environ["PYSPARK_SUBMIT_ARGS"] = (
    '--packages "org.apache.hadoop:hadoop-aws:2.7.3" pyspark-shell'
)

import pyspark

myAccessKey = input()
mySecretKey = input()

spark = (
    pyspark.sql.SparkSession.builder.master("local[*]")
    .config("spark.hadoop.fs.s3a.access.key", myAccessKey)
    .config("spark.hadoop.fs.s3a.secret.key", mySecretKey)
    .getOrCreate()
)

df = spark.read.parquet("s3://myBucket/myKey")
```

Using Spark context for Hadoop 2.6.0

```python
import os

os.environ["PYSPARK_SUBMIT_ARGS"] = (
    "--packages com.amazonaws:aws-java-sdk:1.10.34,org.apache.hadoop:hadoop-aws:2.6.0 pyspark-shell"
)

import pyspark

sc = pyspark.SparkContext("local[*]")

from pyspark.sql import SQLContext

sqlContext = SQLContext(sc)

hadoopConf = sc._jsc.hadoopConfiguration()
myAccessKey = input()
mySecretKey = input()
hadoopConf.set("fs.s3.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem")
hadoopConf.set("fs.s3.awsAccessKeyId", myAccessKey)
hadoopConf.set("fs.s3.awsSecretAccessKey", mySecretKey)

df = sqlContext.read.parquet("s3://myBucket/myKey")
```

Ref: <https://github.com/jupyter/docker-stacks/issues/127>

### Using Local Spark JARs

```{warning}
This recipe is not tested and might be broken.
```

```python
import os

os.environ["PYSPARK_SUBMIT_ARGS"] = (
    "--jars /home/jovyan/spark-streaming-kafka-assembly_2.10-1.6.1.jar pyspark-shell"
)
import pyspark
from pyspark.streaming.kafka import KafkaUtils
from pyspark.streaming import StreamingContext

sc = pyspark.SparkContext()
ssc = StreamingContext(sc, 1)
broker = "<my_broker_ip>"
directKafkaStream = KafkaUtils.createDirectStream(
    ssc, ["test1"], {"metadata.broker.list": broker}
)
directKafkaStream.pprint()
ssc.start()
```

Ref: <https://github.com/jupyter/docker-stacks/issues/154>

### Using spark-packages.org

```{warning}
This recipe is not tested and might be broken.
```

If you'd like to use packages from [spark-packages.org](https://spark-packages.org/), see
[https://gist.github.com/parente/c95fdaba5a9a066efaab](https://gist.github.com/parente/c95fdaba5a9a066efaab)
for an example of how to specify the package identifier in the environment before creating a
SparkContext.

Ref: <https://github.com/jupyter/docker-stacks/issues/43>

### Use jupyter/all-spark-notebooks with an existing Spark/YARN cluster

```{warning}
This recipe is not tested and might be broken.
```

```dockerfile
FROM quay.io/jupyter/all-spark-notebook

# Set env vars for pydoop
ENV HADOOP_HOME=/usr/local/hadoop-2.7.3
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV HADOOP_CONF_HOME=/usr/local/hadoop-2.7.3/etc/hadoop
ENV HADOOP_CONF_DIR=/usr/local/hadoop-2.7.3/etc/hadoop

USER root
# Add proper open-jdk-8 not the jre only, needed for pydoop
RUN echo 'deb https://cdn-fastly.deb.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list && \
    apt-get update --yes && \
    apt-get install --yes --no-install-recommends -t jessie-backports openjdk-8-jdk && \
    rm /etc/apt/sources.list.d/jessie-backports.list && \
    apt-get clean && rm -rf /var/lib/apt/lists/* && \
# Add Hadoop binaries
    wget --progress=dot:giga https://mirrors.ukfast.co.uk/sites/ftp.apache.org/hadoop/common/hadoop-2.7.3/hadoop-2.7.3.tar.gz && \
    tar -xvf hadoop-2.7.3.tar.gz -C /usr/local && \
    chown -R "${NB_USER}:users" /usr/local/hadoop-2.7.3 && \
    rm -f hadoop-2.7.3.tar.gz && \
# Install os dependencies required for pydoop, pyhive
    apt-get update --yes && \
    apt-get install --yes --no-install-recommends build-essential python-dev libsasl2-dev && \
    apt-get clean && rm -rf /var/lib/apt/lists/* && \
# Remove the example hadoop configs and replace
# with those for our cluster.
# Alternatively, this could be mounted as a volume
    rm -f /usr/local/hadoop-2.7.3/etc/hadoop/*

# Download this from ambari/cloudera manager and copy it here
COPY example-hadoop-conf/ /usr/local/hadoop-2.7.3/etc/hadoop/

# Spark-Submit doesn't work unless I set the following
RUN echo "spark.driver.extraJavaOptions -Dhdp.version=2.5.3.0-37" >> /usr/local/spark/conf/spark-defaults.conf && \
    echo "spark.yarn.am.extraJavaOptions -Dhdp.version=2.5.3.0-37" >> /usr/local/spark/conf/spark-defaults.conf && \
    echo "spark.master=yarn" >>  /usr/local/spark/conf/spark-defaults.conf && \
    echo "spark.hadoop.yarn.timeline-service.enabled=false" >> /usr/local/spark/conf/spark-defaults.conf && \
    chown -R "${NB_USER}:users" /usr/local/spark/conf/spark-defaults.conf && \
    # Create an alternative HADOOP_CONF_HOME so we can mount as a volume and repoint
    # using ENV var if needed
    mkdir -p /etc/hadoop/conf/ && \
    chown "${NB_USER}":users /etc/hadoop/conf/

USER ${NB_UID}

# Install useful jupyter extensions and python libraries like :
# - Dashboards
# - PyDoop
# - PyHive
RUN pip install --no-cache-dir 'jupyter_dashboards' 'faker' && \
    jupyter dashboards quick-setup --sys-prefix && \
    pip2 install --no-cache-dir 'pyhive' 'pydoop' 'thrift' 'sasl' 'thrift_sasl' 'faker' && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

USER root
# Ensure we overwrite the kernel config so that toree connects to cluster
RUN jupyter toree install --sys-prefix --spark_opts="\
    --master yarn \
    --deploy-mode client \
    --driver-memory 512m \
    --executor-memory 512m \
    --executor-cores 1 \
    --driver-java-options \
    -Dhdp.version=2.5.3.0-37 \
    --conf spark.hadoop.yarn.timeline-service.enabled=false \
"
USER ${NB_UID}
```

Credit: [britishbadger](https://github.com/britishbadger) from [docker-stacks/issues/369](https://github.com/jupyter/docker-stacks/issues/369)

## Run Server inside an already secured environment (i.e., with no token)

The default security is very good.
There are use cases, encouraged by containers, where the jupyter container and the system it runs within lie inside the security boundary.
It is convenient to launch the server without a password or token in these use cases.
In this case, you should use the `start-notebook.py` script to launch the server with no token:

For JupyterLab:

```bash
docker run -it --rm \
    quay.io/jupyter/base-notebook \
    start-notebook.py --IdentityProvider.token=''
```

For Jupyter Notebook:

```bash
docker run -it --rm \
    -e DOCKER_STACKS_JUPYTER_CMD=notebook \
    quay.io/jupyter/base-notebook \
    start-notebook.py --IdentityProvider.token=''
```

## Enable nbclassic-extension spellchecker for markdown (or any other nbclassic-extension)

```{note}
This recipe only works for NBClassic with Jupyter Notebook < 7.
It is recommended to use [jupyterlab-spellchecker](https://github.com/jupyterlab-contrib/spellchecker) in modern environments.
```

```{literalinclude} recipe_code/spellcheck_notebook_v6.dockerfile
:language: docker
```

## Enable Delta Lake in Spark notebooks

```{warning}
This recipe is not tested and might be broken.
```

Please note that the [Delta Lake](https://delta.io/) packages are only available for Spark version > `3.0`.
By adding the properties to `spark-defaults.conf`, the user no longer needs to enable Delta support in each notebook.

```dockerfile
FROM quay.io/jupyter/pyspark-notebook

RUN mamba install --yes 'delta-spark' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

USER root

RUN echo 'spark.sql.extensions io.delta.sql.DeltaSparkSessionExtension' >> "${SPARK_HOME}/conf/spark-defaults.conf" && \
    echo 'spark.sql.catalog.spark_catalog org.apache.spark.sql.delta.catalog.DeltaCatalog' >> "${SPARK_HOME}/conf/spark-defaults.conf"

USER ${NB_UID}

# Trigger download of delta lake files
RUN echo "from pyspark.sql import SparkSession" > /tmp/init-delta.py && \
    echo "from delta import *" >> /tmp/init-delta.py && \
    echo "spark = configure_spark_with_delta_pip(SparkSession.builder).getOrCreate()" >> /tmp/init-delta.py && \
    python /tmp/init-delta.py && \
    rm /tmp/init-delta.py
```

## Add Custom Fonts in Scipy notebook

```{warning}
This recipe is not tested and might be broken.
```

The example below is a Dockerfile to load Source Han Sans with normal weight, usually used for the web.

```dockerfile
FROM quay.io/jupyter/scipy-notebook

RUN PYV=$(ls "${CONDA_DIR}/lib" | grep ^python) && \
    MPL_DATA="${CONDA_DIR}/lib/${PYV}/site-packages/matplotlib/mpl-data" && \
    wget --progress=dot:giga -P "${MPL_DATA}/fonts/ttf/" https://mirrors.cloud.tencent.com/adobe-fonts/source-han-sans/SubsetOTF/CN/SourceHanSansCN-Normal.otf && \
    sed -i 's/#font.family/font.family/g' "${MPL_DATA}/matplotlibrc" && \
    sed -i 's/#font.sans-serif:/font.sans-serif: Source Han Sans CN,/g' "${MPL_DATA}/matplotlibrc" && \
    sed -i 's/#axes.unicode_minus: True/axes.unicode_minus: False/g' "${MPL_DATA}/matplotlibrc" && \
    rm -rf "/home/${NB_USER}/.cache/matplotlib" && \
    python -c 'import matplotlib.font_manager;print("font loaded: ",("Source Han Sans CN" in [f.name for f in matplotlib.font_manager.fontManager.ttflist]))'
```

## Enable clipboard in pandas on Linux systems

```{warning}
This recipe is not tested and might be broken.
```

```{admonition} Additional notes
    This solution works on Linux host systems.
    It is not required on Windows and won't work on macOS.
```

To enable the `pandas.read_clipboard()` functionality, you need to have `xclip` installed
(installed in `minimal-notebook` and all the inherited images)
and add these options when running `docker`: `-e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix`, i.e.:

```bash
docker run -it --rm \
    -e DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    quay.io/jupyter/minimal-notebook
```

## Install ijavascript kernel in your image

The example below is a Dockerfile to install the [IJavascript kernel](https://github.com/n-riesco/ijavascript).

```{literalinclude} recipe_code/ijavascript.dockerfile
:language: docker
```

## Add Microsoft SQL Server ODBC driver

The following recipe demonstrates how to add functionality to read from and write to an instance of Microsoft SQL server in your notebook.

```{literalinclude} recipe_code/microsoft_odbc.dockerfile
:language: docker
```

You can now use `pyodbc` and `sqlalchemy` to interact with the database.

Pre-built images are hosted in the [Realiserad/jupyter-docker-mssql](https://github.com/Realiserad/jupyter-docker-mssql) repository.

## Add Oracle SQL Instant client, SQL\*Plus, and other tools (Version 21.x)

```{note}
This recipe only works for x86_64 architecture.
```

The following recipe demonstrates how to add functionality to connect to an Oracle Database using [Oracle Instant Client](https://www.oracle.com/database/technologies/instant-client.html)
in your notebook.
This recipe installs version `21.11.0.0.0`.

Nonetheless, go to the [Oracle Instant Client Download page](https://www.oracle.com/es/database/technologies/instant-client/linux-x86-64-downloads.html) for the complete list of versions available.
You may need to perform different steps for older versions;
they may be explained in the "Installation instructions" section of the Downloads page.

```{literalinclude} recipe_code/oracledb.dockerfile
:language: docker
```

## Running Jupyter Docker Stacks with Singularity

You can also start Jupyter Docker Stacks containers using **Singularity** instead of Docker. For example:

```bash
singularity run --bind "${PWD}:/home/${USER}/work" --containall docker://quay.io/jupyter/datascience-notebook:2025-12-31
```

- `--bind "${PWD}:/home/${USER}/work"` mounts your current working directory into the container at `/home/$USER/work`.
  When running the image with Singularity, the container uses your host username inside the container.
  Therefore, the bind target is `/home/${USER}/work` instead of the usual `/home/jovyan/work`.

- `--containall` starts the container in a fully isolated environment, ignoring most of the host’s filesystem and environment except for explicitly bound paths.
  By default, Singularity would bind your home directory automatically.
  If you have Python packages installed there, this may cause conflicts with packages inside the container.
  Using `--containall` avoids such interference.


================================================
FILE: docs/using/running.md
================================================
# Running a Container

Using one of the Jupyter Docker Stacks requires two choices:

1. Which Docker image you wish to use
2. How you wish to start Docker containers from that image

This section provides details about the second.

## Using the Docker CLI

You can launch a local Docker container from the Jupyter Docker Stacks using the [Docker command-line interface](https://docs.docker.com/reference/cli/docker/).
There are numerous ways to configure containers using CLI.
The following are some common patterns.

### Example 1

This command pulls the `jupyter/scipy-notebook` image tagged `2025-12-31` from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 8888.
The server logs appear in the terminal and include a URL to the server.

```bash
docker run -it -p 8888:8888 quay.io/jupyter/scipy-notebook:2025-12-31

# Entered start.sh with args: jupyter lab

# ...

#     To access the server, open this file in a browser:
#         file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
#     Or copy and paste one of these URLs:
#         http://eca4aa01751c:8888/lab?token=d4ac9278f5f5388e88097a3a8ebbe9401be206cfa0b83099
#         http://127.0.0.1:8888/lab?token=d4ac9278f5f5388e88097a3a8ebbe9401be206cfa0b83099
```

Pressing `Ctrl-C` twice shuts down the Server but leaves the container intact on disk for later restart or permanent deletion using commands like the following:

```bash
# list containers
docker ps --all
# CONTAINER ID   IMAGE                                       COMMAND                  CREATED              STATUS                     PORTS     NAMES
# eca4aa01751c   quay.io/jupyter/scipy-notebook:2025-12-31   "tini -g -- start-no…"   About a minute ago   Exited (0) 5 seconds ago             silly_panini

# start the stopped container
docker start --attach -i eca4aa01751c
# Entered start.sh with args: jupyter lab
# ...

# remove the stopped container
docker rm eca4aa01751c
# eca4aa01751c
```

### Example 2

This command pulls the `jupyter/r-notebook` image tagged `2025-12-31` from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server and exposes the server on host port 10000.
The server logs appear in the terminal and include a URL to the Server but with the internal container port (8888) instead of the correct host port (10000).

```bash
docker run -it --rm -p 10000:8888 -v "${PWD}":/home/jovyan/work quay.io/jupyter/r-notebook:2025-12-31
```

Pressing `Ctrl-C` twice shuts down the Server and immediately destroys the Docker container.
New files and changes in `~/work` in the container will be preserved.
Any other changes made in the container will be lost.

```{note}
By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`.
So, new notebooks will be saved there, unless you change the directory in the file browser.

To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to the previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`.
```

### Example 3

This command pulls the `jupyter/all-spark-notebook` image currently tagged `latest` from Quay.io if an image tagged `latest` is not already present on the local host.
It then starts a container named `notebook` running a JupyterLab server and exposes the server on a randomly selected port.

```bash
docker run --detach -P --name notebook quay.io/jupyter/all-spark-notebook
```

where:

- `--detach`: will run the container in detached mode

You can also use the following docker commands to see the port and Jupyter Server token:

```bash
# get the random host port assigned to the container port 8888
docker port notebook 8888
# 0.0.0.0:49153
# :::49153

# get the notebook token from the logs
docker logs --tail 3 notebook
    # Or copy and paste one of these URLs:
    #     http://878f1a9b4dfa:8888/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3
    #  or http://127.0.0.1:8888/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3
```

Together, the URL to visit on the host machine to access the server, in this case, is <http://127.0.0.1:49153/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3>.

The container runs in the background until stopped and/or removed by additional Docker commands:

```bash
# stop the container
docker stop notebook
# notebook

# remove the container permanently
docker rm notebook
# notebook
```

## Using the Podman CLI

An alternative to using the Docker CLI is to use the Podman CLI.
Podman is mostly compatible with Docker.

### Podman example

If we use Podman instead of Docker in the situation given in _Example 2_, it will look like this:

The example makes use of rootless Podman; in other words, the Podman command is run from a regular user account.
In a Bash shell, set the shell variables _uid_ and _gid_ to the UID and GID of the user _jovyan_ in the container.

```bash
uid=1000
gid=100
```

Set the shell variables _subuidSize_ and _subgidSize_ to the number of subordinate UIDs and GIDs, respectively.

```bash
subuidSize=$(( $(podman info --format "{{ range .Host.IDMappings.UIDMap }}+{{.Size }}{{end }}" ) - 1 ))
subgidSize=$(( $(podman info --format "{{ range .Host.IDMappings.GIDMap }}+{{.Size }}{{end }}" ) - 1 ))
```

This command pulls the `quay.io/jupyter/r-notebook` image tagged `2025-12-31` from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 10000.
The server logs appear in the terminal and include a URL to the server but with the internal container port (8888) instead of the correct host port (10000).

```bash
podman run -it --rm -p 10000:8888 \
    -v "${PWD}":/home/jovyan/work --user $uid:$gid \
    --uidmap $uid:0:1 --uidmap 0:1:$uid --uidmap $(($uid+1)):$(($uid+1)):$(($subuidSize-$uid)) \
    --gidmap $gid:0:1 --gidmap 0:1:$gid --gidmap $(($gid+1)):$(($gid+1)):$(($subgidSize-$gid)) \
    quay.io/jupyter/r-notebook:2025-12-31
```

```{warning}
The `podman run` options `--uidmap` and `--gidmap` can be used to map the container user _jovyan_ to the regular user on the host when running rootless Podman.
The same Podman command should not be run with sudo (i.e. running rootful Podman)
because then the mapping would map the container user _jovyan_ to the root user on the host.
It's a good security practice to run programs with as few privileges as possible.
```

```{note}
The `podman run` command in the example above maps all subuids and subgids of the user into the container.
That works fine but is actually more than needed.
The `podman run` option `--userns=auto` will, for instance, not be possible to use as long as there are no unused subuids and subgids available.
The example could be improved by investigating more in detail which UIDs and GIDs need to be available in the container and then only map them.
```

Pressing `Ctrl-C` twice shuts down the Server and immediately destroys the Docker container.
New files and changes in `~/work` in the container will be preserved.
Any other changes made in the container will be lost.

## Using Binder

A [Binder](https://mybinder.org/) is a service that allows you to create a
Download .txt
gitextract_w6y_9n58/

├── .devcontainer/
│   ├── Dockerfile
│   └── devcontainer.json
├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── blank.yml
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actions/
│   │   ├── apply-single-tags/
│   │   │   └── action.yml
│   │   ├── create-dev-env/
│   │   │   └── action.yml
│   │   ├── free-disk-space/
│   │   │   └── action.yml
│   │   └── load-image/
│   │       └── action.yml
│   ├── dependabot.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── contributed-recipes.yml
│       ├── docker-build-test-upload.yml
│       ├── docker-tag-merge.yml
│       ├── docker-tag-push-merge.yml
│       ├── docker-tag-push.yml
│       ├── docker-wiki-update.yml
│       ├── docker.yml
│       ├── pre-commit.yml
│       ├── registry-move.yml
│       ├── registry-overviews.yml
│       └── sphinx.yml
├── .gitignore
├── .hadolint.yaml
├── .markdownlint.yaml
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── SECURITY.md
├── binder/
│   ├── Dockerfile
│   └── README.ipynb
├── docs/
│   ├── conf.py
│   ├── contributing/
│   │   ├── features.md
│   │   ├── issues.md
│   │   ├── lint.md
│   │   ├── packages.md
│   │   ├── recipes.md
│   │   ├── stacks.md
│   │   └── tests.md
│   ├── index.rst
│   ├── maintaining/
│   │   ├── new-images-and-packages-policy.md
│   │   ├── tagging.md
│   │   ├── tagging_examples/
│   │   │   ├── docker_runner.py
│   │   │   └── git_helper.py
│   │   └── tasks.md
│   ├── requirements.txt
│   └── using/
│       ├── changelog.md
│       ├── common.md
│       ├── custom-images.md
│       ├── faq.md
│       ├── recipe_code/
│       │   ├── custom_environment.dockerfile
│       │   ├── dask_jupyterlab.dockerfile
│       │   ├── docker-bake.custom-python.hcl
│       │   ├── generate_matrix.py
│       │   ├── ijavascript.dockerfile
│       │   ├── jupyterhub_version.dockerfile
│       │   ├── mamba_install.dockerfile
│       │   ├── manpage_install.dockerfile
│       │   ├── microsoft_odbc.dockerfile
│       │   ├── oracledb.dockerfile
│       │   ├── pip_install.dockerfile
│       │   ├── requirements.txt
│       │   ├── rise_jupyterlab.dockerfile
│       │   ├── spellcheck_notebook_v6.dockerfile
│       │   └── xgboost.dockerfile
│       ├── recipes.md
│       ├── running.md
│       ├── selecting.md
│       ├── specifics.md
│       └── troubleshooting.md
├── examples/
│   ├── README.md
│   ├── docker-compose/
│   │   ├── README.md
│   │   ├── bin/
│   │   │   ├── letsencrypt.sh
│   │   │   ├── sl-dns.sh
│   │   │   ├── softlayer.sh
│   │   │   └── vbox.sh
│   │   └── notebook/
│   │       ├── Dockerfile
│   │       ├── build.sh
│   │       ├── down.sh
│   │       ├── env.sh
│   │       ├── letsencrypt-notebook.yml
│   │       ├── notebook.yml
│   │       ├── secure-notebook.yml
│   │       └── up.sh
│   ├── make-deploy/
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── letsencrypt.makefile
│   │   ├── self-signed.makefile
│   │   ├── softlayer.makefile
│   │   └── virtualbox.makefile
│   ├── openshift/
│   │   ├── README.md
│   │   └── templates.json
│   └── source-to-image/
│       ├── README.md
│       ├── assemble
│       ├── run
│       ├── save-artifacts
│       └── templates.json
├── images/
│   ├── all-spark-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── base-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── docker_healthcheck.py
│   │   ├── jupyter_server_config.py
│   │   ├── start-notebook.py
│   │   ├── start-notebook.sh
│   │   ├── start-singleuser.py
│   │   └── start-singleuser.sh
│   ├── datascience-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── docker-stacks-foundation/
│   │   ├── .dockerignore
│   │   ├── 10activate-conda-env.sh
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── fix-permissions
│   │   ├── initial-condarc
│   │   ├── run-hooks.sh
│   │   └── start.sh
│   ├── julia-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── minimal-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── Rprofile.site
│   │   └── setup-scripts/
│   │       ├── activate_notebook_custom_env.py
│   │       ├── setup-julia-packages.bash
│   │       └── setup_julia.py
│   ├── pyspark-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── ipython_kernel_config.py
│   │   └── setup_spark.py
│   ├── pytorch-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── cuda12/
│   │   │   └── Dockerfile
│   │   └── cuda13/
│   │       └── Dockerfile
│   ├── r-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   ├── scipy-notebook/
│   │   ├── .dockerignore
│   │   ├── Dockerfile
│   │   └── README.md
│   └── tensorflow-notebook/
│       ├── .dockerignore
│       ├── Dockerfile
│       ├── README.md
│       └── cuda/
│           ├── 20tensorboard-proxy-env.sh
│           ├── Dockerfile
│           └── nvidia-lib-dirs.sh
├── mypy.ini
├── requirements-dev.txt
├── tagging/
│   ├── README.md
│   ├── __init__.py
│   ├── apps/
│   │   ├── __init__.py
│   │   ├── apply_tags.py
│   │   ├── common_cli_arguments.py
│   │   ├── config.py
│   │   ├── merge_tags.py
│   │   ├── write_manifest.py
│   │   └── write_tags_file.py
│   ├── hierarchy/
│   │   ├── __init__.py
│   │   ├── get_manifests.py
│   │   ├── get_taggers.py
│   │   └── images_hierarchy.py
│   ├── manifests/
│   │   ├── __init__.py
│   │   ├── apt_packages.py
│   │   ├── build_info.py
│   │   ├── conda_environment.py
│   │   ├── julia_packages.py
│   │   ├── manifest_interface.py
│   │   ├── r_packages.py
│   │   └── spark_info.py
│   ├── taggers/
│   │   ├── __init__.py
│   │   ├── date.py
│   │   ├── sha.py
│   │   ├── tagger_interface.py
│   │   ├── ubuntu_version.py
│   │   └── versions.py
│   └── utils/
│       ├── __init__.py
│       ├── docker_runner.py
│       ├── get_platform.py
│       ├── get_prefix.py
│       ├── git_helper.py
│       └── quoted_output.py
├── tests/
│   ├── README.md
│   ├── __init__.py
│   ├── by_image/
│   │   ├── all-spark-notebook/
│   │   │   ├── data/
│   │   │   │   ├── local_sparkR.ipynb
│   │   │   │   └── local_sparklyr.ipynb
│   │   │   └── test_spark_r_nbconvert.py
│   │   ├── base-notebook/
│   │   │   ├── data/
│   │   │   │   └── check_listening.py
│   │   │   ├── test_container_options.py
│   │   │   ├── test_healthcheck.py
│   │   │   ├── test_ips.py
│   │   │   ├── test_notebook.py
│   │   │   ├── test_pandoc.py
│   │   │   └── test_start_container.py
│   │   ├── datascience-notebook/
│   │   │   ├── test_julia_datascience.py
│   │   │   ├── test_mimetypes.py
│   │   │   └── test_pluto_datascience.py
│   │   ├── docker-stacks-foundation/
│   │   │   ├── data/
│   │   │   │   └── run-hooks/
│   │   │   │       ├── change/
│   │   │   │       │   ├── a.sh
│   │   │   │       │   ├── b.sh
│   │   │   │       │   └── c.sh
│   │   │   │       ├── executables/
│   │   │   │       │   ├── executable.py
│   │   │   │       │   ├── non_executable.py
│   │   │   │       │   └── run-me.sh
│   │   │   │       ├── failures/
│   │   │   │       │   ├── a.sh
│   │   │   │       │   ├── b.py
│   │   │   │       │   ├── c.sh
│   │   │   │       │   └── d.sh
│   │   │   │       ├── sh-files/
│   │   │   │       │   ├── executable.sh
│   │   │   │       │   └── non-executable.sh
│   │   │   │       └── unset/
│   │   │   │           ├── a.sh
│   │   │   │           ├── b.sh
│   │   │   │           └── c.sh
│   │   │   ├── test_outdated.py
│   │   │   ├── test_package_managers.py
│   │   │   ├── test_packages.py
│   │   │   ├── test_python_version.py
│   │   │   ├── test_run_hooks.py
│   │   │   ├── test_units.py
│   │   │   └── test_user_options.py
│   │   ├── julia-notebook/
│   │   │   ├── test_julia.py
│   │   │   └── test_pluto.py
│   │   ├── minimal-notebook/
│   │   │   ├── data/
│   │   │   │   ├── notebook_math.ipynb
│   │   │   │   └── notebook_svg.ipynb
│   │   │   └── test_nbconvert.py
│   │   ├── pyspark-notebook/
│   │   │   ├── data/
│   │   │   │   ├── issue_1168.ipynb
│   │   │   │   └── local_pyspark.ipynb
│   │   │   ├── test_spark.py
│   │   │   ├── test_spark_nbconvert.py
│   │   │   └── units/
│   │   │       ├── unit_pandas_version.py
│   │   │       └── unit_spark.py
│   │   ├── pytorch-notebook/
│   │   │   └── units/
│   │   │       └── unit_pytorch.py
│   │   ├── r-notebook/
│   │   │   └── test_R_mimetypes.py
│   │   ├── scipy-notebook/
│   │   │   ├── data/
│   │   │   │   ├── cython/
│   │   │   │   │   ├── helloworld.pyx
│   │   │   │   │   └── setup.py
│   │   │   │   └── matplotlib/
│   │   │   │       ├── matplotlib_1.py
│   │   │   │       └── matplotlib_fonts_1.py
│   │   │   ├── test_cython.py
│   │   │   ├── test_extensions.py
│   │   │   ├── test_matplotlib.py
│   │   │   └── units/
│   │   │       └── unit_pandas.py
│   │   └── tensorflow-notebook/
│   │       └── units/
│   │           └── unit_tensorflow.py
│   ├── conftest.py
│   ├── hierarchy/
│   │   ├── __init__.py
│   │   ├── get_test_dirs.py
│   │   └── images_hierarchy.py
│   ├── pytest.ini
│   ├── run_tests.py
│   ├── shared_checks/
│   │   ├── R_mimetype_check.py
│   │   ├── __init__.py
│   │   ├── nbconvert_check.py
│   │   └── pluto_check.py
│   └── utils/
│       ├── __init__.py
│       ├── conda_package_helper.py
│       └── tracked_container.py
└── wiki/
    ├── Home.md
    ├── __init__.py
    ├── config.py
    ├── manifest_time.py
    └── update_wiki.py
Download .txt
SYMBOL INDEX (196 symbols across 66 files)

FILE: docs/using/recipe_code/generate_matrix.py
  function extract_base_image (line 14) | def extract_base_image(dockerfile: Path) -> str:
  function get_platform (line 24) | def get_platform(runs_on: str) -> str:
  function generate_matrix (line 29) | def generate_matrix() -> dict[str, list[dict[str, str]]]:

FILE: images/minimal-notebook/setup-scripts/setup_julia.py
  function unify_aarch64 (line 21) | def unify_aarch64(platform: str) -> str:
  function get_latest_julia_url (line 28) | def get_latest_julia_url() -> tuple[str, str]:
  function download_julia (line 49) | def download_julia(julia_url: str) -> None:
  function configure_julia (line 63) | def configure_julia(julia_version: str) -> None:

FILE: images/pyspark-notebook/setup_spark.py
  function get_all_refs (line 22) | def get_all_refs(url: str) -> list[str]:
  function get_latest_spark_version (line 31) | def get_latest_spark_version() -> str:
  function download_spark (line 57) | def download_spark(
  function configure_spark (line 98) | def configure_spark(spark_dir_name: str, spark_home: Path) -> None:

FILE: tagging/apps/apply_tags.py
  function apply_tags (line 17) | def apply_tags(config: Config) -> None:

FILE: tagging/apps/common_cli_arguments.py
  function common_arguments_parser (line 10) | def common_arguments_parser(

FILE: tagging/apps/config.py
  class Config (line 8) | class Config:
    method full_image (line 21) | def full_image(self) -> str:

FILE: tagging/apps/merge_tags.py
  function read_local_tags_from_files (line 25) | def read_local_tags_from_files(config: Config) -> set[str]:
  function inspect_manifest (line 50) | def inspect_manifest(tag: str) -> None:
  function find_platform_tags (line 56) | def find_platform_tags(merged_tag: str) -> list[str]:
  function merge_tags (line 72) | def merge_tags(merged_tag: str, push_to_registry: bool) -> None:

FILE: tagging/apps/write_manifest.py
  function get_build_history_line (line 25) | def get_build_history_line(config: Config, container: Container, filenam...
  function write_build_history_line (line 50) | def write_build_history_line(
  function get_manifest (line 63) | def get_manifest(config: Config, container: Container, commit_hash_tag: ...
  function write_manifest (line 89) | def write_manifest(
  function write_all (line 102) | def write_all(config: Config) -> None:

FILE: tagging/apps/write_tags_file.py
  function get_tags (line 15) | def get_tags(config: Config) -> list[str]:
  function write_tags_file (line 34) | def write_tags_file(config: Config) -> None:

FILE: tagging/hierarchy/get_manifests.py
  function get_manifests (line 7) | def get_manifests(image: str | None) -> list[ManifestInterface]:

FILE: tagging/hierarchy/get_taggers.py
  function get_taggers (line 7) | def get_taggers(image: str | None) -> list[TaggerInterface]:

FILE: tagging/hierarchy/images_hierarchy.py
  class ImageDescription (line 19) | class ImageDescription:

FILE: tagging/manifests/apt_packages.py
  function apt_packages_manifest (line 9) | def apt_packages_manifest(container: Container) -> MarkdownPiece:

FILE: tagging/manifests/build_info.py
  class BuildInfoConfig (line 15) | class BuildInfoConfig:
    method full_image (line 24) | def full_image(self) -> str:
  function build_info_manifest (line 28) | def build_info_manifest(config: BuildInfoConfig) -> MarkdownPiece:

FILE: tagging/manifests/conda_environment.py
  function conda_environment_manifest (line 10) | def conda_environment_manifest(container: Container) -> MarkdownPiece:

FILE: tagging/manifests/julia_packages.py
  function julia_packages_manifest (line 9) | def julia_packages_manifest(container: Container) -> MarkdownPiece:

FILE: tagging/manifests/manifest_interface.py
  class MarkdownPiece (line 10) | class MarkdownPiece:
    method __post_init__ (line 14) | def __post_init__(self) -> None:
    method get_str (line 18) | def get_str(self) -> str:

FILE: tagging/manifests/r_packages.py
  function r_packages_manifest (line 9) | def r_packages_manifest(container: Container) -> MarkdownPiece:

FILE: tagging/manifests/spark_info.py
  function spark_info_manifest (line 9) | def spark_info_manifest(container: Container) -> MarkdownPiece:

FILE: tagging/taggers/date.py
  function date_tagger (line 8) | def date_tagger(container: Container) -> str:

FILE: tagging/taggers/sha.py
  function commit_sha_tagger (line 8) | def commit_sha_tagger(container: Container) -> str:

FILE: tagging/taggers/ubuntu_version.py
  function ubuntu_version_tagger (line 8) | def ubuntu_version_tagger(container: Container) -> str:

FILE: tagging/taggers/versions.py
  function _get_program_version (line 8) | def _get_program_version(container: Container, program: str) -> str:
  function _get_pip_package_version (line 12) | def _get_pip_package_version(container: Container, package: str) -> str:
  function python_tagger (line 24) | def python_tagger(container: Container) -> str:
  function python_major_minor_tagger (line 28) | def python_major_minor_tagger(container: Container) -> str:
  function mamba_tagger (line 33) | def mamba_tagger(container: Container) -> str:
  function conda_tagger (line 37) | def conda_tagger(container: Container) -> str:
  function jupyter_notebook_tagger (line 41) | def jupyter_notebook_tagger(container: Container) -> str:
  function jupyter_lab_tagger (line 45) | def jupyter_lab_tagger(container: Container) -> str:
  function jupyter_hub_tagger (line 49) | def jupyter_hub_tagger(container: Container) -> str:
  function r_tagger (line 53) | def r_tagger(container: Container) -> str:
  function julia_tagger (line 57) | def julia_tagger(container: Container) -> str:
  function tensorflow_tagger (line 61) | def tensorflow_tagger(container: Container) -> str:
  function pytorch_tagger (line 68) | def pytorch_tagger(container: Container) -> str:
  function spark_tagger (line 72) | def spark_tagger(container: Container) -> str:
  function java_tagger (line 85) | def java_tagger(container: Container) -> str:

FILE: tagging/utils/docker_runner.py
  class DockerRunner (line 12) | class DockerRunner:
    method __init__ (line 13) | def __init__(
    method __enter__ (line 24) | def __enter__(self) -> Container:
    method __exit__ (line 33) | def __exit__(
    method exec_cmd (line 45) | def exec_cmd(container: Container, cmd: str) -> str:

FILE: tagging/utils/get_platform.py
  function unify_aarch64 (line 8) | def unify_aarch64(platform: str) -> str:
  function get_platform (line 15) | def get_platform() -> str:

FILE: tagging/utils/get_prefix.py
  function get_file_prefix_for_platform (line 8) | def get_file_prefix_for_platform(*, platform: str, variant: str) -> str:
  function _get_tag_prefix_for_platform (line 12) | def _get_tag_prefix_for_platform(*, platform: str, variant: str) -> str:
  function get_file_prefix (line 18) | def get_file_prefix(variant: str) -> str:
  function get_tag_prefix (line 23) | def get_tag_prefix(variant: str) -> str:

FILE: tagging/utils/git_helper.py
  class GitHelper (line 9) | class GitHelper:
    method commit_hash (line 11) | def commit_hash() -> str:
    method commit_hash_tag (line 15) | def commit_hash_tag() -> str:
    method commit_message (line 19) | def commit_message() -> str:

FILE: tagging/utils/quoted_output.py
  function quoted_output (line 10) | def quoted_output(container: Container, cmd: str) -> str:

FILE: tests/by_image/all-spark-notebook/test_spark_r_nbconvert.py
  function test_spark_r_nbconvert (line 24) | def test_spark_r_nbconvert(

FILE: tests/by_image/base-notebook/data/check_listening.py
  function make_get_request (line 10) | def make_get_request() -> None:
  function check_addrs (line 24) | def check_addrs(family: socket.AddressFamily) -> None:
  function test_connect (line 49) | def test_connect() -> None:

FILE: tests/by_image/base-notebook/test_container_options.py
  function test_cli_args (line 14) | def test_cli_args(
  function test_nb_user_change (line 32) | def test_nb_user_change(container: TrackedContainer) -> None:
  function test_unsigned_ssl (line 56) | def test_unsigned_ssl(
  function test_custom_internal_port (line 93) | def test_custom_internal_port(

FILE: tests/by_image/base-notebook/test_healthcheck.py
  function get_healthy_status (line 13) | def get_healthy_status(
  function test_healthy (line 82) | def test_healthy(
  function test_healthy_with_proxy (line 115) | def test_healthy_with_proxy(
  function test_not_healthy (line 138) | def test_not_healthy(

FILE: tests/by_image/base-notebook/test_ips.py
  function ipv6_network (line 18) | def ipv6_network(docker_client: docker.DockerClient) -> Generator[str, N...
  function test_ipv46 (line 36) | def test_ipv46(container: TrackedContainer, ipv6_network: str) -> None:

FILE: tests/by_image/base-notebook/test_notebook.py
  function test_secured_server (line 8) | def test_secured_server(

FILE: tests/by_image/base-notebook/test_pandoc.py
  function test_pandoc (line 10) | def test_pandoc(container: TrackedContainer) -> None:

FILE: tests/by_image/base-notebook/test_start_container.py
  function test_start_notebook (line 31) | def test_start_notebook(
  function test_tini_entrypoint (line 65) | def test_tini_entrypoint(

FILE: tests/by_image/datascience-notebook/test_julia_datascience.py
  function test_julia (line 6) | def test_julia(container: TrackedContainer) -> None:

FILE: tests/by_image/datascience-notebook/test_mimetypes.py
  function test_mimetypes (line 7) | def test_mimetypes(container: TrackedContainer) -> None:

FILE: tests/by_image/datascience-notebook/test_pluto_datascience.py
  function test_pluto_proxy (line 9) | def test_pluto_proxy(

FILE: tests/by_image/docker-stacks-foundation/test_outdated.py
  function test_outdated_packages (line 15) | def test_outdated_packages(container: TrackedContainer, requested_only: ...

FILE: tests/by_image/docker-stacks-foundation/test_package_managers.py
  function test_package_manager (line 12) | def test_package_manager(

FILE: tests/by_image/docker-stacks-foundation/test_packages.py
  function is_r_package (line 67) | def is_r_package(package: str) -> bool:
  function get_package_import_name (line 72) | def get_package_import_name(package: str) -> str:
  function check_import_python_package (line 79) | def check_import_python_package(container: TrackedContainer, package: st...
  function check_import_r_package (line 84) | def check_import_r_package(container: TrackedContainer, package: str) ->...
  function _check_import_packages (line 89) | def _check_import_packages(
  function get_r_packages (line 108) | def get_r_packages(package_helper: CondaPackageHelper) -> list[str]:
  function test_r_packages (line 117) | def test_r_packages(container: TrackedContainer) -> None:
  function get_python_packages (line 123) | def get_python_packages(package_helper: CondaPackageHelper) -> list[str]:
  function test_python_packages (line 132) | def test_python_packages(container: TrackedContainer) -> None:

FILE: tests/by_image/docker-stacks-foundation/test_python_version.py
  function test_python_version (line 11) | def test_python_version(container: TrackedContainer) -> None:
  function test_python_pinned_version (line 26) | def test_python_pinned_version(container: TrackedContainer) -> None:

FILE: tests/by_image/docker-stacks-foundation/test_run_hooks.py
  function test_run_hooks_zero_args (line 14) | def test_run_hooks_zero_args(container: TrackedContainer) -> None:
  function test_run_hooks_two_args (line 26) | def test_run_hooks_two_args(container: TrackedContainer) -> None:
  function test_run_hooks_missing_dir (line 42) | def test_run_hooks_missing_dir(container: TrackedContainer) -> None:
  function test_run_hooks_dir_is_file (line 58) | def test_run_hooks_dir_is_file(container: TrackedContainer) -> None:
  function test_run_hooks_empty_dir (line 74) | def test_run_hooks_empty_dir(container: TrackedContainer) -> None:
  function run_source_in_dir (line 88) | def run_source_in_dir(
  function test_run_hooks_change (line 115) | def test_run_hooks_change(container: TrackedContainer) -> None:
  function test_run_hooks_executables (line 125) | def test_run_hooks_executables(container: TrackedContainer) -> None:
  function test_run_hooks_failures (line 137) | def test_run_hooks_failures(container: TrackedContainer) -> None:
  function test_run_hooks_sh_files (line 161) | def test_run_hooks_sh_files(container: TrackedContainer) -> None:
  function test_run_hooks_unset (line 168) | def test_run_hooks_unset(container: TrackedContainer) -> None:

FILE: tests/by_image/docker-stacks-foundation/test_units.py
  function test_units (line 11) | def test_units(container: TrackedContainer) -> None:

FILE: tests/by_image/docker-stacks-foundation/test_user_options.py
  function test_uid_change (line 14) | def test_uid_change(container: TrackedContainer) -> None:
  function test_gid_change (line 25) | def test_gid_change(container: TrackedContainer) -> None:
  function test_nb_user_change (line 37) | def test_nb_user_change(container: TrackedContainer) -> None:
  function test_chown_extra (line 82) | def test_chown_extra(container: TrackedContainer) -> None:
  function test_chown_home (line 106) | def test_chown_home(container: TrackedContainer) -> None:
  function test_sudo (line 124) | def test_sudo(container: TrackedContainer) -> None:
  function test_sudo_path (line 135) | def test_sudo_path(container: TrackedContainer) -> None:
  function test_sudo_path_without_grant (line 146) | def test_sudo_path_without_grant(container: TrackedContainer) -> None:
  function test_group_add (line 156) | def test_group_add(container: TrackedContainer) -> None:
  function test_set_uid (line 174) | def test_set_uid(container: TrackedContainer) -> None:
  function test_set_uid_and_nb_user (line 191) | def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
  function test_container_not_delete_bind_mount (line 207) | def test_container_not_delete_bind_mount(
  function test_jupyter_env_vars_to_unset (line 234) | def test_jupyter_env_vars_to_unset(
  function test_secure_path (line 258) | def test_secure_path(container: TrackedContainer, tmp_path: pathlib.Path...
  function test_startsh_multiple_exec (line 278) | def test_startsh_multiple_exec(container: TrackedContainer) -> None:
  function test_rootless_triplet_change (line 296) | def test_rootless_triplet_change(container: TrackedContainer) -> None:
  function test_rootless_triplet_home (line 309) | def test_rootless_triplet_home(container: TrackedContainer) -> None:
  function test_rootless_triplet_sudo (line 321) | def test_rootless_triplet_sudo(container: TrackedContainer) -> None:
  function test_log_stderr (line 332) | def test_log_stderr(container: TrackedContainer) -> None:

FILE: tests/by_image/julia-notebook/test_julia.py
  function test_julia (line 6) | def test_julia(container: TrackedContainer) -> None:

FILE: tests/by_image/julia-notebook/test_pluto.py
  function test_pluto_proxy (line 9) | def test_pluto_proxy(

FILE: tests/by_image/minimal-notebook/test_nbconvert.py
  function test_nbconvert (line 17) | def test_nbconvert(

FILE: tests/by_image/pyspark-notebook/test_spark.py
  function test_spark_shell (line 10) | def test_spark_shell(container: TrackedContainer) -> None:

FILE: tests/by_image/pyspark-notebook/test_spark_nbconvert.py
  function test_spark_nbconvert (line 17) | def test_spark_nbconvert(

FILE: tests/by_image/r-notebook/test_R_mimetypes.py
  function test_mimetypes (line 7) | def test_mimetypes(container: TrackedContainer) -> None:

FILE: tests/by_image/scipy-notebook/test_cython.py
  function test_cython (line 10) | def test_cython(container: TrackedContainer) -> None:

FILE: tests/by_image/scipy-notebook/test_extensions.py
  function test_check_extension (line 21) | def test_check_extension(container: TrackedContainer, extension: str) ->...

FILE: tests/by_image/scipy-notebook/test_matplotlib.py
  function test_matplotlib (line 29) | def test_matplotlib(

FILE: tests/conftest.py
  function http_client (line 20) | def http_client() -> requests.Session:
  function docker_client (line 30) | def docker_client() -> docker.DockerClient:
  function pytest_addoption (line 37) | def pytest_addoption(parser: pytest.Parser) -> None:
  function image_name (line 58) | def image_name(request: pytest.FixtureRequest) -> str:
  function container (line 70) | def container(
  function free_host_port (line 87) | def free_host_port() -> Generator[int]:

FILE: tests/hierarchy/get_test_dirs.py
  function get_test_dirs (line 13) | def get_test_dirs(image: str | None) -> list[Path]:

FILE: tests/run_tests.py
  function test_image (line 16) | def test_image(*, registry: str, owner: str, image: str) -> None:

FILE: tests/shared_checks/R_mimetype_check.py
  function check_r_mimetypes (line 10) | def check_r_mimetypes(container: TrackedContainer) -> None:

FILE: tests/shared_checks/nbconvert_check.py
  function check_nbconvert (line 11) | def check_nbconvert(

FILE: tests/shared_checks/pluto_check.py
  function check_pluto_proxy (line 14) | def check_pluto_proxy(

FILE: tests/utils/conda_package_helper.py
  class CondaPackageHelper (line 39) | class CondaPackageHelper:
    method __init__ (line 42) | def __init__(self, container: TrackedContainer):
    method installed_packages (line 47) | def installed_packages(self) -> dict[str, set[str]]:
    method requested_packages (line 54) | def requested_packages(self) -> dict[str, set[str]]:
    method _parse_package_versions (line 63) | def _parse_package_versions(env_export: str) -> dict[str, set[str]]:
    method available_packages (line 101) | def available_packages(self) -> dict[str, set[str]]:
    method _extract_available (line 109) | def _extract_available(lines: str) -> defaultdict[str, set[str]]:
    method find_updatable_packages (line 119) | def find_updatable_packages(self, requested_only: bool) -> list[dict[s...
    method semantic_cmp (line 135) | def semantic_cmp(version_string: str) -> tuple[int, ...]:
    method get_outdated_summary (line 160) | def get_outdated_summary(
    method get_outdated_table (line 172) | def get_outdated_table(self, updatable: list[dict[str, str]]) -> str:

FILE: tests/utils/tracked_container.py
  class TrackedContainer (line 12) | class TrackedContainer:
    method __init__ (line 24) | def __init__(
    method run_detached (line 33) | def run_detached(self, **kwargs: Any) -> None:
    method get_logs (line 57) | def get_logs(self, *, stdout: bool = True, stderr: bool = True) -> str:
    method get_health (line 63) | def get_health(self) -> str:
    method exec_cmd (line 68) | def exec_cmd(self, cmd: str, **kwargs: Any) -> str:
    method run_and_wait (line 86) | def run_and_wait(
    method run_and_wait (line 98) | def run_and_wait(
    method run_and_wait (line 109) | def run_and_wait(
    method get_errors (line 160) | def get_errors(logs: str) -> list[str]:
    method get_warnings (line 164) | def get_warnings(logs: str) -> list[str]:
    method _lines_starting_with (line 175) | def _lines_starting_with(logs: str, pattern: LiteralString) -> list[str]:
    method remove (line 178) | def remove(self) -> None:

FILE: wiki/config.py
  class Config (line 8) | class Config:

FILE: wiki/manifest_time.py
  function get_manifest_timestamp (line 6) | def get_manifest_timestamp(manifest_file: Path) -> str:
  function get_manifest_year_month (line 19) | def get_manifest_year_month(manifest_file: Path) -> str:

FILE: wiki/update_wiki.py
  class YearMonthFile (line 26) | class YearMonthFile:
  class Statistics (line 32) | class Statistics:
  function calculate_monthly_stat (line 38) | def calculate_monthly_stat(
  class YearFiles (line 63) | class YearFiles:
  function generate_home_wiki_tables (line 68) | def generate_home_wiki_tables(repository: str, all_years: list[YearFiles...
  function write_home_wiki_page (line 122) | def write_home_wiki_page(wiki_dir: Path, repository: str) -> None:
  function update_monthly_wiki_page (line 151) | def update_monthly_wiki_page(wiki_dir: Path, build_history_line: str) ->...
  function remove_old_manifests (line 177) | def remove_old_manifests(wiki_dir: Path) -> None:
  function copy_manifest_files (line 190) | def copy_manifest_files(config: Config) -> None:
  function update_wiki (line 203) | def update_wiki(config: Config) -> None:
Condensed preview — 273 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (539K chars).
[
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 283,
    "preview": "FROM mcr.microsoft.com/devcontainers/python:3.13\n\nCOPY requirements-dev.txt /tmp/requirements-dev.txt\nCOPY docs/requirem"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 646,
    "preview": "{\n  \"name\": \"Jupyter Docker Stacks\",\n  \"build\": {\n    \"context\": \"..\",\n    \"dockerfile\": \"Dockerfile\"\n  },\n\n  \"features\""
  },
  {
    "path": ".flake8",
    "chars": 98,
    "preview": "[flake8]\nmax-line-length = 88\nselect = C, E, F, W, B, B950\nextend-ignore = E203, E501, E704, W503\n"
  },
  {
    "path": ".gitattributes",
    "chars": 19,
    "preview": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/blank.yml",
    "chars": 441,
    "preview": "name: \"(maintainers only) Blank issue\"\ndescription: For maintainers only\nlabels: []\n\nbody:\n  - type: markdown\n    attrib"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 4029,
    "preview": "name: Bug report\ndescription: Create a report to help us improve\nlabels: [\"type:Bug\"]\n\nbody:\n  - type: markdown\n    attr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 571,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 📖 - Jupyter Docker Stacks documentation\n    url: https://jupyter-do"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 2478,
    "preview": "name: Feature request\ndescription: Suggest a new feature for this project\nlabels: [\"type:Enhancement\"]\n\nbody:\n  - type: "
  },
  {
    "path": ".github/actions/apply-single-tags/action.yml",
    "chars": 1835,
    "preview": "name: Apply single platform tags\ndescription: Download the image tar, load it to Docker and apply tags to it\n\ninputs:\n  "
  },
  {
    "path": ".github/actions/create-dev-env/action.yml",
    "chars": 789,
    "preview": "name: Build environment\ndescription: Create a build environment\n\nruns:\n  using: composite\n  steps:\n    - name: Set Up Py"
  },
  {
    "path": ".github/actions/free-disk-space/action.yml",
    "chars": 832,
    "preview": "name: \"Free Disk Space (Ubuntu)\"\ndescription: \"A GitHub Action to free up disk space on an Ubuntu GitHub Actions runner."
  },
  {
    "path": ".github/actions/load-image/action.yml",
    "chars": 938,
    "preview": "name: Load Docker image\ndescription: Download the image tar and load it to Docker\n\ninputs:\n  image:\n    description: Ima"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 1361,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 486,
    "preview": "## Describe your changes\n\n## Issue ticket if applicable\n\n<!-- Example - Fix: https://github.com/jupyter/docker-stacks/is"
  },
  {
    "path": ".github/workflows/contributed-recipes.yml",
    "chars": 3286,
    "preview": "name: Test the contributed recipes\n\nenv:\n  REGISTRY: quay.io\n  OWNER: ${{ github.repository_owner }}\n\non:\n  schedule:\n  "
  },
  {
    "path": ".github/workflows/docker-build-test-upload.yml",
    "chars": 5262,
    "preview": "name: Download a parent image, build a new one, and test it; upload the image, tags, build history line and manifest to "
  },
  {
    "path": ".github/workflows/docker-tag-merge.yml",
    "chars": 2426,
    "preview": "name: Merge single platform tags\n\nenv:\n  REGISTRY: quay.io\n  PUSH_TO_REGISTRY: ${{ (github.repository_owner == 'jupyter'"
  },
  {
    "path": ".github/workflows/docker-tag-push-merge.yml",
    "chars": 1049,
    "preview": "name: Download a Docker image and its tags from GitHub artifacts, apply them, and push the image to the Registry; then m"
  },
  {
    "path": ".github/workflows/docker-tag-push.yml",
    "chars": 2613,
    "preview": "name: Download a Docker image and its tags from GitHub artifacts, apply them, and push the image to the Registry\n\nenv:\n "
  },
  {
    "path": ".github/workflows/docker-wiki-update.yml",
    "chars": 2210,
    "preview": "name: Download build history lines and manifests from GitHub artifacts and push them to the GitHub wiki\n# We're doing ev"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 15049,
    "preview": "name: Docker Stacks\n\n# [FAST_BUILD] in the PR title makes this workflow only build\n# the `jupyter/docker-stacks-foundati"
  },
  {
    "path": ".github/workflows/pre-commit.yml",
    "chars": 717,
    "preview": "name: Run pre-commit hooks\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\n\npermissions:\n  "
  },
  {
    "path": ".github/workflows/registry-move.yml",
    "chars": 1973,
    "preview": "name: Move some images from Docker Hub to Quay.io\n\nenv:\n  OWNER: ${{ github.repository_owner }}\n  PUSH_TO_REGISTRY: ${{ "
  },
  {
    "path": ".github/workflows/registry-overviews.yml",
    "chars": 1358,
    "preview": "name: Update Registry overviews\n\nenv:\n  OWNER: ${{ github.repository_owner }}\n\non:\n  push:\n    branches:\n      - main\n  "
  },
  {
    "path": ".github/workflows/sphinx.yml",
    "chars": 1793,
    "preview": "name: Build Sphinx Documentation and check links\n\non:\n  schedule:\n    # Weekly, at 03:00 on Monday UTC\n    - cron: \"0 3 "
  },
  {
    "path": ".gitignore",
    "chars": 3883,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".hadolint.yaml",
    "chars": 46,
    "preview": "---\nignored:\n  - DL3006\n  - DL3008\n  - DL3013\n"
  },
  {
    "path": ".markdownlint.yaml",
    "chars": 146,
    "preview": "# Default state for all rules\ndefault: true\n\n# MD013/line-length - Line length\nMD013:\n  # Number of characters\n  line_le"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 5783,
    "preview": "---\n# pre-commit is a tool to perform a predefined set of tasks manually and/or\n# automatically before git commits are m"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 1093,
    "preview": "# Read the Docs configuration file for Sphinx projects\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7744,
    "preview": "# Changelog\n\nThis changelog only contains breaking and/or significant changes manually introduced to this repository (us"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 177,
    "preview": "# Project `jupyter/docker-stacks` Code of Conduct\n\nPlease see the [Project Jupyter Code of Conduct](https://github.com/j"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1635,
    "preview": "Thanks for contributing!\nPlease see the **Contributor Guide** section in [the documentation](https://jupyter-docker-stac"
  },
  {
    "path": "LICENSE.md",
    "chars": 1563,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2001-2015, IPython Development Team\n\nCopyright (c) 2015-, Jupyter Development Team\n\n"
  },
  {
    "path": "Makefile",
    "chars": 4921,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n.PHONY: docs help t"
  },
  {
    "path": "README.md",
    "chars": 11143,
    "preview": "# Jupyter Docker Stacks\n\n[![GitHub Actions badge](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml/"
  },
  {
    "path": "SECURITY.md",
    "chars": 341,
    "preview": "# Security Policy\n\n## Supported Versions\n\nJupyter Docker Stacks only provides security updates for the latest version of"
  },
  {
    "path": "binder/Dockerfile",
    "chars": 677,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# https://quay.io/"
  },
  {
    "path": "binder/README.ipynb",
    "chars": 3183,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# jupyter/base-notebook on Binder\\n"
  },
  {
    "path": "docs/conf.py",
    "chars": 2886,
    "preview": "# Configuration file for the Sphinx documentation builder.\n#\n# For the full list of built-in configuration values, see t"
  },
  {
    "path": "docs/contributing/features.md",
    "chars": 2800,
    "preview": "# New Features\n\nThank you for contributing to the Jupyter Docker Stacks!\nWe review pull requests for new features (e.g.,"
  },
  {
    "path": "docs/contributing/issues.md",
    "chars": 1573,
    "preview": "# Project Issues\n\nWe appreciate you taking the time to report an issue you encountered while using the Jupyter Docker St"
  },
  {
    "path": "docs/contributing/lint.md",
    "chars": 2901,
    "preview": "# Lint\n\nTo enforce some rules, **linters** are used in this project.\nLinters can be run either during the **development "
  },
  {
    "path": "docs/contributing/packages.md",
    "chars": 975,
    "preview": "# Package Updates\n\nGenerally, we do not pin package versions in our `Dockerfile`s.\nDependency resolution is a difficult "
  },
  {
    "path": "docs/contributing/recipes.md",
    "chars": 1030,
    "preview": "# New Recipes\n\nWe welcome contributions of [recipes](../using/recipes.md), which are short examples of using, configurin"
  },
  {
    "path": "docs/contributing/stacks.md",
    "chars": 7321,
    "preview": "# Community Stacks\n\nWe love to see the community create and share new Jupyter Docker images.\nWe've put together a [cooki"
  },
  {
    "path": "docs/contributing/tests.md",
    "chars": 2363,
    "preview": "# Image Tests\n\nWe greatly appreciate Pull Requests that extend the automated tests that vet the basic functionality of t"
  },
  {
    "path": "docs/index.rst",
    "chars": 928,
    "preview": ".. include:: ../README.md\n   :parser: myst_parser.sphinx_\n\nTable of Contents\n-----------------\n\n.. toctree::\n   :maxdept"
  },
  {
    "path": "docs/maintaining/new-images-and-packages-policy.md",
    "chars": 1682,
    "preview": "# New images / packages policy\n\nThere are many things we consider while adding new images and packages.\n\nHere is a non-e"
  },
  {
    "path": "docs/maintaining/tagging.md",
    "chars": 3778,
    "preview": "# Tags and manifests\n\nThe main purpose of the source code in [the `tagging` folder](https://github.com/jupyter/docker-st"
  },
  {
    "path": "docs/maintaining/tagging_examples/docker_runner.py",
    "chars": 245,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom tagging.utils."
  },
  {
    "path": "docs/maintaining/tagging_examples/git_helper.py",
    "chars": 243,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom tagging.utils."
  },
  {
    "path": "docs/maintaining/tasks.md",
    "chars": 3302,
    "preview": "# Maintainer Playbook\n\n## Merging Pull Requests\n\nTo build new images and publish them to the Registry, do the following:"
  },
  {
    "path": "docs/requirements.txt",
    "chars": 293,
    "preview": "# ReadTheDocs environment contains old package versions preinstalled\n# So, to ensure we have modern packages, we pin min"
  },
  {
    "path": "docs/using/changelog.md",
    "chars": 79,
    "preview": "```{include} ../../CHANGELOG.md\n\n```\n\n<!-- markdownlint-disable-file MD041 -->\n"
  },
  {
    "path": "docs/using/common.md",
    "chars": 16323,
    "preview": "# Common Features\n\nExcept for `jupyter/docker-stacks-foundation`, a container launched from any Jupyter Docker Stacks im"
  },
  {
    "path": "docs/using/custom-images.md",
    "chars": 3923,
    "preview": "# Building a custom set of images\n\nThis section describes how to build a custom set of images.\nIt may be helpful if you "
  },
  {
    "path": "docs/using/faq.md",
    "chars": 2377,
    "preview": "# Frequently Asked Questions (FAQ)\n\n## How to persist user data\n\nThere are two types of data you might want to persist.\n"
  },
  {
    "path": "docs/using/recipe_code/custom_environment.dockerfile",
    "chars": 2226,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/minimal-notebook\nFROM $BASE_IMAGE\n\n# Name your environment and choose the Python version\n"
  },
  {
    "path": "docs/using/recipe_code/dask_jupyterlab.dockerfile",
    "chars": 288,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\n# Install the Dask dashboard\nRUN mamba install --yes 'das"
  },
  {
    "path": "docs/using/recipe_code/docker-bake.custom-python.hcl",
    "chars": 1049,
    "preview": "group \"default\" {\n    targets = [\"custom-notebook\"]\n}\n\ntarget \"foundation\" {\n    context = \"https://github.com/jupyter/d"
  },
  {
    "path": "docs/using/recipe_code/generate_matrix.py",
    "chars": 1840,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "docs/using/recipe_code/ijavascript.dockerfile",
    "chars": 576,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nUSER root\n\nRUN apt-get update --yes && \\\n    apt-get inst"
  },
  {
    "path": "docs/using/recipe_code/jupyterhub_version.dockerfile",
    "chars": 235,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nRUN mamba install --yes 'jupyterhub-singleuser==5.2.1' &&"
  },
  {
    "path": "docs/using/recipe_code/mamba_install.dockerfile",
    "chars": 481,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nRUN mamba install --yes 'flake8' && \\\n    mamba clean --a"
  },
  {
    "path": "docs/using/recipe_code/manpage_install.dockerfile",
    "chars": 616,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\n# Fix: https://github.com/hadolint/hadolint/wiki/DL4006\n#"
  },
  {
    "path": "docs/using/recipe_code/microsoft_odbc.dockerfile",
    "chars": 1328,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\n# Fix: https://github.com/hadolint/hadolint/wiki/DL4006\n#"
  },
  {
    "path": "docs/using/recipe_code/oracledb.dockerfile",
    "chars": 3102,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nUSER root\n\n# Install Java & Oracle SQL Instant Client\nRUN"
  },
  {
    "path": "docs/using/recipe_code/pip_install.dockerfile",
    "chars": 481,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\n# Install in the default python3 environment\nRUN pip inst"
  },
  {
    "path": "docs/using/recipe_code/requirements.txt",
    "chars": 10,
    "preview": "autoflake\n"
  },
  {
    "path": "docs/using/recipe_code/rise_jupyterlab.dockerfile",
    "chars": 222,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nRUN mamba install --yes 'jupyterlab_rise' && \\\n    mamba "
  },
  {
    "path": "docs/using/recipe_code/spellcheck_notebook_v6.dockerfile",
    "chars": 478,
    "preview": "# Using Docker Hub here, because this image is old and not pushed to Quay.io\nARG BASE_IMAGE=docker.io/jupyter/base-noteb"
  },
  {
    "path": "docs/using/recipe_code/xgboost.dockerfile",
    "chars": 217,
    "preview": "ARG BASE_IMAGE=quay.io/jupyter/base-notebook\nFROM $BASE_IMAGE\n\nRUN mamba install --yes 'py-xgboost' && \\\n    mamba clean"
  },
  {
    "path": "docs/using/recipes.md",
    "chars": 19211,
    "preview": "# Contributed Recipes\n\nUsers sometimes share interesting ways of using the Jupyter Docker Stacks.\nWe encourage users to "
  },
  {
    "path": "docs/using/running.md",
    "chars": 9011,
    "preview": "# Running a Container\n\nUsing one of the Jupyter Docker Stacks requires two choices:\n\n1. Which Docker image you wish to u"
  },
  {
    "path": "docs/using/selecting.md",
    "chars": 24031,
    "preview": "# Selecting an Image\n\n- [Core Stacks](#core-stacks)\n- [Image Relationships](#image-relationships)\n- [Community Stacks](#"
  },
  {
    "path": "docs/using/specifics.md",
    "chars": 10909,
    "preview": "# Image Specifics\n\nThis page provides details about features specific to one or more images.\n\n## Apache Spark™\n\n### Spec"
  },
  {
    "path": "docs/using/troubleshooting.md",
    "chars": 13655,
    "preview": "# Troubleshooting Common Problems\n\nWhen troubleshooting, you may see unexpected behaviors or receive an error message.\nT"
  },
  {
    "path": "examples/README.md",
    "chars": 134,
    "preview": "# Examples\n\nThese examples are not tested and might not work.\nPlease, send PRs if you start using these examples and see"
  },
  {
    "path": "examples/docker-compose/README.md",
    "chars": 5766,
    "preview": "# Docker Compose example\n\nThis example demonstrate how to deploy docker-stack notebook containers to any Docker Machine-"
  },
  {
    "path": "examples/docker-compose/bin/letsencrypt.sh",
    "chars": 1532,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Use "
  },
  {
    "path": "examples/docker-compose/bin/sl-dns.sh",
    "chars": 803,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nset -e"
  },
  {
    "path": "examples/docker-compose/bin/softlayer.sh",
    "chars": 410,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Set "
  },
  {
    "path": "examples/docker-compose/bin/vbox.sh",
    "chars": 314,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Set "
  },
  {
    "path": "examples/docker-compose/notebook/Dockerfile",
    "chars": 529,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Pick your favori"
  },
  {
    "path": "examples/docker-compose/notebook/build.sh",
    "chars": 348,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nDIR=\"$"
  },
  {
    "path": "examples/docker-compose/notebook/down.sh",
    "chars": 407,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nDIR=\"$"
  },
  {
    "path": "examples/docker-compose/notebook/env.sh",
    "chars": 497,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Set "
  },
  {
    "path": "examples/docker-compose/notebook/letsencrypt-notebook.yml",
    "chars": 655,
    "preview": "---\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nversion: \"2\"\n\n"
  },
  {
    "path": "examples/docker-compose/notebook/notebook.yml",
    "chars": 345,
    "preview": "---\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nversion: \"2\"\n\n"
  },
  {
    "path": "examples/docker-compose/notebook/secure-notebook.yml",
    "chars": 413,
    "preview": "---\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nversion: \"2\"\n\n"
  },
  {
    "path": "examples/docker-compose/notebook/up.sh",
    "chars": 1836,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nset -e"
  },
  {
    "path": "examples/make-deploy/Dockerfile",
    "chars": 529,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Pick your favori"
  },
  {
    "path": "examples/make-deploy/Makefile",
    "chars": 1333,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n.PHONY: help check"
  },
  {
    "path": "examples/make-deploy/README.md",
    "chars": 4459,
    "preview": "# Make deploy example\n\nThis folder contains a Makefile and a set of supporting files demonstrating how to run a docker-s"
  },
  {
    "path": "examples/make-deploy/letsencrypt.makefile",
    "chars": 2349,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# BE CAREFUL when "
  },
  {
    "path": "examples/make-deploy/self-signed.makefile",
    "chars": 428,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nself-signed-notebo"
  },
  {
    "path": "examples/make-deploy/softlayer.makefile",
    "chars": 1193,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nsoftlayer-vm: expo"
  },
  {
    "path": "examples/make-deploy/virtualbox.makefile",
    "chars": 397,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nvirtualbox-vm: exp"
  },
  {
    "path": "examples/openshift/README.md",
    "chars": 8889,
    "preview": "# OpenShift example\n\nThis example provides templates for deploying the Jupyter Project docker-stacks images to OpenShift"
  },
  {
    "path": "examples/openshift/templates.json",
    "chars": 4705,
    "preview": "{\n  \"kind\": \"Template\",\n  \"apiVersion\": \"v1\",\n  \"metadata\": {\n    \"name\": \"jupyter-notebook\",\n    \"annotations\": {\n     "
  },
  {
    "path": "examples/source-to-image/README.md",
    "chars": 8405,
    "preview": "# Custom Jupyter Notebook images\n\nThis example provides scripts for building custom Jupyter Notebook images containing n"
  },
  {
    "path": "examples/source-to-image/assemble",
    "chars": 1249,
    "preview": "#!/bin/bash\n\nset -x\n\nset -eo pipefail\n\n# Remove any 'environment.yml' or 'requirements.txt' files which may\n# have been "
  },
  {
    "path": "examples/source-to-image/run",
    "chars": 76,
    "preview": "#!/bin/bash\n\n# Start up the notebook instance.\n\nexec start-notebook.py \"$@\"\n"
  },
  {
    "path": "examples/source-to-image/save-artifacts",
    "chars": 45,
    "preview": "#!/bin/bash\n\ntar cf - --files-from /dev/null\n"
  },
  {
    "path": "examples/source-to-image/templates.json",
    "chars": 10577,
    "preview": "{\n  \"kind\": \"List\",\n  \"apiVersion\": \"v1\",\n  \"items\": [\n    {\n      \"kind\": \"Template\",\n      \"apiVersion\": \"v1\",\n      \""
  },
  {
    "path": "images/all-spark-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/all-spark-notebook/Dockerfile",
    "chars": 1352,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/all-spark-notebook/README.md",
    "chars": 671,
    "preview": "# Jupyter Notebook Python, R, Spark Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project buil"
  },
  {
    "path": "images/base-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/base-notebook/Dockerfile",
    "chars": 3808,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/base-notebook/README.md",
    "chars": 523,
    "preview": "# Base Jupyter Notebook Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and pushe"
  },
  {
    "path": "images/base-notebook/docker_healthcheck.py",
    "chars": 1255,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "images/base-notebook/jupyter_server_config.py",
    "chars": 1812,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n# mypy: ignore-erro"
  },
  {
    "path": "images/base-notebook/start-notebook.py",
    "chars": 1532,
    "preview": "#!/usr/bin/env python\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licens"
  },
  {
    "path": "images/base-notebook/start-notebook.sh",
    "chars": 152,
    "preview": "#!/bin/bash\n# Shim to emit warning and call start-notebook.py\necho \"WARNING: Use start-notebook.py instead\"\n\nexec /usr/l"
  },
  {
    "path": "images/base-notebook/start-singleuser.py",
    "chars": 731,
    "preview": "#!/usr/bin/env python\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licens"
  },
  {
    "path": "images/base-notebook/start-singleuser.sh",
    "chars": 158,
    "preview": "#!/bin/bash\n# Shim to emit warning and call start-singleuser.py\necho \"WARNING: Use start-singleuser.py instead\"\n\nexec /u"
  },
  {
    "path": "images/datascience-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/datascience-notebook/Dockerfile",
    "chars": 1908,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/datascience-notebook/README.md",
    "chars": 545,
    "preview": "# Jupyter Notebook Data Science Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds a"
  },
  {
    "path": "images/docker-stacks-foundation/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/docker-stacks-foundation/10activate-conda-env.sh",
    "chars": 406,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# This"
  },
  {
    "path": "images/docker-stacks-foundation/Dockerfile",
    "chars": 6684,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# Ubuntu 24.04 (no"
  },
  {
    "path": "images/docker-stacks-foundation/README.md",
    "chars": 542,
    "preview": "# Foundation Jupyter Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and pushes t"
  },
  {
    "path": "images/docker-stacks-foundation/fix-permissions",
    "chars": 1041,
    "preview": "#!/bin/bash\n# Set permissions on a directory\n# After any installation, if a directory needs to be (human) user-writable,"
  },
  {
    "path": "images/docker-stacks-foundation/initial-condarc",
    "chars": 163,
    "preview": "# Conda configuration see https://conda.io/projects/conda/en/latest/configuration.html\n\nauto_update_conda: false\nshow_ch"
  },
  {
    "path": "images/docker-stacks-foundation/run-hooks.sh",
    "chars": 1665,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# iden"
  },
  {
    "path": "images/docker-stacks-foundation/start.sh",
    "chars": 12672,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nset -e"
  },
  {
    "path": "images/julia-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/julia-notebook/Dockerfile",
    "chars": 1079,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/julia-notebook/README.md",
    "chars": 526,
    "preview": "# Jupyter Notebook Julia Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and push"
  },
  {
    "path": "images/minimal-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/minimal-notebook/Dockerfile",
    "chars": 1946,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/minimal-notebook/README.md",
    "chars": 532,
    "preview": "# Minimal Jupyter Notebook Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and pu"
  },
  {
    "path": "images/minimal-notebook/Rprofile.site",
    "chars": 292,
    "preview": "# Add R mimetype to specify how the plot returns from R to the browser.\n# https://notebook.community/andrie/jupyter-note"
  },
  {
    "path": "images/minimal-notebook/setup-scripts/activate_notebook_custom_env.py",
    "chars": 701,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "images/minimal-notebook/setup-scripts/setup-julia-packages.bash",
    "chars": 2445,
    "preview": "#!/bin/bash\nset -exuo pipefail\n# Requirements:\n# - Run as a non-root user\n# - The JULIA_PKGDIR environment variable is s"
  },
  {
    "path": "images/minimal-notebook/setup-scripts/setup_julia.py",
    "chars": 3050,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "images/pyspark-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/pyspark-notebook/Dockerfile",
    "chars": 2804,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/pyspark-notebook/README.md",
    "chars": 664,
    "preview": "# Jupyter Notebook Python, Spark Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds "
  },
  {
    "path": "images/pyspark-notebook/ipython_kernel_config.py",
    "chars": 659,
    "preview": "# Configuration file for ipython-kernel.\n# See <https://ipython.readthedocs.io/en/stable/config/options/kernel.html>\n\n# "
  },
  {
    "path": "images/pyspark-notebook/setup_spark.py",
    "chars": 4209,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "images/pytorch-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/pytorch-notebook/Dockerfile",
    "chars": 741,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/pytorch-notebook/README.md",
    "chars": 546,
    "preview": "# Jupyter Notebook PyTorch Deep Learning Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project"
  },
  {
    "path": "images/pytorch-notebook/cuda12/Dockerfile",
    "chars": 1203,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/pytorch-notebook/cuda13/Dockerfile",
    "chars": 1203,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/r-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/r-notebook/Dockerfile",
    "chars": 1641,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/r-notebook/README.md",
    "chars": 514,
    "preview": "# Jupyter Notebook R Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and pushes t"
  },
  {
    "path": "images/scipy-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/scipy-notebook/Dockerfile",
    "chars": 2321,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/scipy-notebook/README.md",
    "chars": 538,
    "preview": "# Jupyter Notebook Scientific Python Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> project bui"
  },
  {
    "path": "images/tensorflow-notebook/.dockerignore",
    "chars": 26,
    "preview": "# Documentation\nREADME.md\n"
  },
  {
    "path": "images/tensorflow-notebook/Dockerfile",
    "chars": 1209,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/tensorflow-notebook/README.md",
    "chars": 677,
    "preview": "# Jupyter Notebook TensorFlow Deep Learning Stack\n\nGitHub Actions in the <https://github.com/jupyter/docker-stacks> proj"
  },
  {
    "path": "images/tensorflow-notebook/cuda/20tensorboard-proxy-env.sh",
    "chars": 295,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nset -e"
  },
  {
    "path": "images/tensorflow-notebook/cuda/Dockerfile",
    "chars": 1789,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nARG REGISTRY=quay.i"
  },
  {
    "path": "images/tensorflow-notebook/cuda/nvidia-lib-dirs.sh",
    "chars": 459,
    "preview": "#!/bin/bash\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n# This"
  },
  {
    "path": "mypy.ini",
    "chars": 1084,
    "preview": "# Mypy is an optional static type checker for Python that aims to combine\n# the benefits of dynamic (or \"duck\") typing a"
  },
  {
    "path": "requirements-dev.txt",
    "chars": 228,
    "preview": "docker\nplumbum\npre-commit\npytest\npytest-rerunfailures\n# `pytest-xdist` is a plugin that provides the `--numprocesses` fl"
  },
  {
    "path": "tagging/README.md",
    "chars": 227,
    "preview": "# Docker stacks tagging and manifest creation\n\nPlease, refer to the [tagging section of documentation](https://jupyter-d"
  },
  {
    "path": "tagging/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/apps/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/apps/apply_tags.py",
    "chars": 1185,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "tagging/apps/common_cli_arguments.py",
    "chars": 2319,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport argparse\nfro"
  },
  {
    "path": "tagging/apps/config.py",
    "chars": 524,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom dataclasses im"
  },
  {
    "path": "tagging/apps/merge_tags.py",
    "chars": 3749,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "tagging/apps/write_manifest.py",
    "chars": 4696,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "tagging/apps/write_tags_file.py",
    "chars": 1800,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "tagging/hierarchy/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/hierarchy/get_manifests.py",
    "chars": 502,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom tagging.hierar"
  },
  {
    "path": "tagging/hierarchy/get_taggers.py",
    "chars": 484,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom tagging.hierar"
  },
  {
    "path": "tagging/hierarchy/images_hierarchy.py",
    "chars": 3009,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom dataclasses im"
  },
  {
    "path": "tagging/manifests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/manifests/apt_packages.py",
    "chars": 468,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/manifests/build_info.py",
    "chars": 1673,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport textwrap\nfro"
  },
  {
    "path": "tagging/manifests/conda_environment.py",
    "chars": 712,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/manifests/julia_packages.py",
    "chars": 630,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/manifests/manifest_interface.py",
    "chars": 563,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom collections.ab"
  },
  {
    "path": "tagging/manifests/r_packages.py",
    "chars": 604,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/manifests/spark_info.py",
    "chars": 511,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/taggers/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/taggers/date.py",
    "chars": 281,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport datetime\n\nfr"
  },
  {
    "path": "tagging/taggers/sha.py",
    "chars": 289,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/taggers/tagger_interface.py",
    "chars": 232,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom collections.ab"
  },
  {
    "path": "tagging/taggers/ubuntu_version.py",
    "chars": 571,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/taggers/versions.py",
    "chars": 2761,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom docker.models."
  },
  {
    "path": "tagging/utils/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tagging/utils/docker_runner.py",
    "chars": 1968,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport logging\nfrom"
  },
  {
    "path": "tagging/utils/get_platform.py",
    "chars": 434,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport platform\n\nAL"
  },
  {
    "path": "tagging/utils/get_prefix.py",
    "chars": 760,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom tagging.utils."
  },
  {
    "path": "tagging/utils/git_helper.py",
    "chars": 651,
    "preview": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD Licen"
  },
  {
    "path": "tagging/utils/quoted_output.py",
    "chars": 768,
    "preview": "# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nimport textwrap\n\nfr"
  },
  {
    "path": "tests/README.md",
    "chars": 191,
    "preview": "# Docker stacks testing\n\nPlease, refer to the [testing section of documentation](https://jupyter-docker-stacks.readthedo"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/by_image/all-spark-notebook/data/local_sparkR.ipynb",
    "chars": 804,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": "
  },
  {
    "path": "tests/by_image/all-spark-notebook/data/local_sparklyr.ipynb",
    "chars": 942,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": "
  }
]

// ... and 73 more files (download for full content)

About this extraction

This page contains the full source code of the jupyter/docker-stacks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 273 files (487.0 KB), approximately 134.0k tokens, and a symbol index with 196 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!