├── .gitignore ├── notebook ├── .gitkeep └── hello-jamsocket.ipynb ├── requirements.txt ├── empty.ipynb ├── freeze.sh ├── LICENSE ├── Dockerfile ├── .github └── workflows │ └── docker-publish.yml ├── freeze.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | -------------------------------------------------------------------------------- /notebook/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | notebook 2 | pandas 3 | scipy 4 | numpy 5 | matplotlib 6 | -------------------------------------------------------------------------------- /empty.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 5 6 | } 7 | -------------------------------------------------------------------------------- /freeze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | python3 -m virtualenv env 6 | . env/bin/activate 7 | pip install -r requirements.txt 8 | pip freeze > freeze.txt 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Drifting in Space Corp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /notebook/hello-jamsocket.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "7b30fe0e-d4b7-42d4-8714-fd29ab746020", 7 | "metadata": {}, 8 | "source": [ 9 | "# Hi!\n", 10 | "\n", 11 | "This is a notebook hosted by [Jamsocket](https://jamsocket.com/).\n", 12 | "\n", 13 | "You can clone the [jamsocket-jupyter-notebook](https://github.com/drifting-in-space/jamsocket-jupyter-notebook) repo and replace this with whatever you want." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "id": "54fe8c01-0929-47be-a80b-c4d682c56dae", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "print('Hello, Jamsocket user!')" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3 (ipykernel)", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.9.2" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 5 48 | } 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim-bookworm 2 | 3 | # This entire section is only needed while we are using notebook directly from git instead of pypi. 4 | # If it were permanent, we would want to do a multi-stage built, but it's not. 5 | RUN apt-get update 6 | RUN apt-get install -y build-essential 7 | RUN apt-get install -y git 8 | RUN apt-get install -y curl 9 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - 10 | RUN apt-get install -y nodejs 11 | 12 | RUN useradd -m jupyter 13 | USER jupyter 14 | WORKDIR /home/jupyter 15 | 16 | RUN python3 -m venv notebook-env 17 | COPY --chown=jupyter freeze.txt ./ 18 | COPY empty.ipynb ./ 19 | 20 | RUN /home/jupyter/notebook-env/bin/pip install --upgrade pip 21 | RUN /home/jupyter/notebook-env/bin/pip install -r freeze.txt 22 | 23 | # Warm up the kernel. Disabled until we can prove that this helps. 24 | #RUN /home/jupyter/notebook-env/bin/pip install nbconvert 25 | #RUN /home/jupyter/notebook-env/bin/jupyter nbconvert --to markdown --execute empty.ipynb 26 | 27 | COPY --chown=jupyter notebook ./notebook 28 | RUN /home/jupyter/notebook-env/bin/ipython profile create default 29 | 30 | ENV PATH=/home/jupyter/notebook-env/bin:$PATH 31 | 32 | WORKDIR /home/jupyter/notebook 33 | 34 | # https://stackoverflow.com/a/75552789 35 | RUN /home/jupyter/notebook-env/bin/jupyter \ 36 | labextension disable "@jupyterlab/apputils-extension:announcements" 37 | 38 | CMD /home/jupyter/notebook-env/bin/jupyter \ 39 | ${JUPYTER_SUBCOMMAND:-notebook} \ 40 | --ip 0.0.0.0 \ 41 | --port $PORT \ 42 | --no-browser \ 43 | --ServerApp.token="" \ 44 | --ServerApp.base_url="${SESSION_BACKEND_STATIC_TOKEN}" \ 45 | --JupyterNotebookApp.default_url="/doc/tree/hello-jamsocket.ipynb" 46 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Jamsocket 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | REGISTRY: registry.jamsocket.com 11 | IMAGE_NAME: ${{ secrets.JAMSOCKET_ACCOUNT }}/jupyter-notebook 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | id-token: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v3 24 | 25 | # this splits the JAMSOCKET_TOKEN on a period and takes the first portion as the docker registry user 26 | - name: Set JAMCR_USER from token 27 | env: 28 | JAMSOCKET_TOKEN: ${{ secrets.JAMSOCKET_TOKEN }} 29 | id: token_public 30 | run: echo "JAMCR_USER=${JAMSOCKET_TOKEN%\.*}" >> $GITHUB_ENV 31 | 32 | # this splits the JAMSOCKET_TOKEN on a period and takes the second portion as the docker registry pass 33 | - name: Set JAMCR_PASS from token 34 | env: 35 | JAMSOCKET_TOKEN: ${{ secrets.JAMSOCKET_TOKEN }} 36 | id: token_secret 37 | run: echo "JAMCR_PASS=${JAMSOCKET_TOKEN#*\.}" >> $GITHUB_ENV 38 | 39 | - name: Log into registry ${{ env.REGISTRY }} 40 | if: github.event_name != 'pull_request' 41 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 42 | with: 43 | registry: ${{ env.REGISTRY }} 44 | username: ${{ env.JAMCR_USER }} 45 | password: ${{ env.JAMCR_PASS }} 46 | 47 | - name: Extract Docker metadata 48 | id: meta 49 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 50 | with: 51 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 52 | tags: | 53 | type=sha,enable=true,priority=100,prefix=sha-,suffix=,format=short 54 | type=raw,value=latest 55 | 56 | - name: Build and push Docker image 57 | id: build-and-push 58 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a 59 | with: 60 | context: . 61 | push: ${{ github.event_name != 'pull_request' }} 62 | tags: ${{ steps.meta.outputs.tags }} 63 | labels: ${{ steps.meta.outputs.labels }} 64 | -------------------------------------------------------------------------------- /freeze.txt: -------------------------------------------------------------------------------- 1 | anyio==4.4.0 2 | appnope==0.1.4 3 | argon2-cffi==23.1.0 4 | argon2-cffi-bindings==21.2.0 5 | arrow==1.3.0 6 | asttokens==2.4.1 7 | async-lru==2.0.4 8 | attrs==23.2.0 9 | Babel==2.15.0 10 | beautifulsoup4==4.12.3 11 | bleach==6.1.0 12 | certifi==2024.7.4 13 | cffi==1.16.0 14 | charset-normalizer==3.3.2 15 | comm==0.2.2 16 | contourpy==1.2.1 17 | cycler==0.12.1 18 | debugpy==1.8.2 19 | decorator==5.1.1 20 | defusedxml==0.7.1 21 | executing==2.0.1 22 | fastjsonschema==2.20.0 23 | fonttools==4.53.1 24 | fqdn==1.5.1 25 | h11==0.14.0 26 | httpcore==1.0.5 27 | httpx==0.27.0 28 | idna==3.7 29 | ipykernel==6.29.5 30 | ipython==8.26.0 31 | isoduration==20.11.0 32 | jedi==0.19.1 33 | Jinja2==3.1.4 34 | json5==0.9.25 35 | jsonpointer==3.0.0 36 | jsonschema==4.23.0 37 | jsonschema-specifications==2023.12.1 38 | jupyter-events==0.10.0 39 | jupyter-lsp==2.2.5 40 | jupyter_client==8.6.2 41 | jupyter_core==5.7.2 42 | jupyter_server==2.14.2 43 | jupyter_server_terminals==0.5.3 44 | jupyterlab==4.2.4 45 | jupyterlab_pygments==0.3.0 46 | jupyterlab_server==2.27.3 47 | kiwisolver==1.4.5 48 | MarkupSafe==2.1.5 49 | matplotlib==3.9.1 50 | matplotlib-inline==0.1.7 51 | mistune==3.0.2 52 | nbclient==0.10.0 53 | nbconvert==7.16.4 54 | nbformat==5.10.4 55 | nest-asyncio==1.6.0 56 | notebook==7.2.1 57 | notebook_shim==0.2.4 58 | numpy==2.0.0 59 | overrides==7.7.0 60 | packaging==24.1 61 | pandas==2.2.2 62 | pandocfilters==1.5.1 63 | parso==0.8.4 64 | pexpect==4.9.0 65 | pillow==10.4.0 66 | platformdirs==4.2.2 67 | prometheus_client==0.20.0 68 | prompt_toolkit==3.0.47 69 | psutil==6.0.0 70 | ptyprocess==0.7.0 71 | pure-eval==0.2.2 72 | pycparser==2.22 73 | Pygments==2.18.0 74 | pyparsing==3.1.2 75 | python-dateutil==2.9.0.post0 76 | python-json-logger==2.0.7 77 | pytz==2024.1 78 | PyYAML==6.0.1 79 | pyzmq==26.0.3 80 | referencing==0.35.1 81 | requests==2.32.3 82 | rfc3339-validator==0.1.4 83 | rfc3986-validator==0.1.1 84 | rpds-py==0.19.0 85 | scipy==1.14.0 86 | Send2Trash==1.8.3 87 | six==1.16.0 88 | sniffio==1.3.1 89 | soupsieve==2.5 90 | stack-data==0.6.3 91 | terminado==0.18.1 92 | tinycss2==1.3.0 93 | tornado==6.4.1 94 | traitlets==5.14.3 95 | types-python-dateutil==2.9.0.20240316 96 | typing_extensions==4.12.2 97 | tzdata==2024.1 98 | uri-template==1.3.0 99 | urllib3==2.2.2 100 | wcwidth==0.2.13 101 | webcolors==24.6.0 102 | webencodings==0.5.1 103 | websocket-client==1.8.0 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jamsocket-jupyter-notebook 2 | Base repo for running Jupyter Notebooks on the Jamsocket platform 3 | 4 | ## Deploying 5 | 6 | 1. Log in to [Jamsocket](https://jamsocket.com) and create a new service called `jupyter-notebook`. ([Sign up](https://auth.jamsocket.com/signup) for a Jamsocket account if you don’t already have one. You can do everything listed here on the free plan.) 7 | 2. Reach out to us on [Discord](https://discord.gg/RFrDbMVKxv) or [by email](mailto:hi@jamsocket.com) to ask us to **enable Jamsocket compatibility mode** for that service. (This will soon be exposed directly in the web UI, but it's a manual step for now.) 8 | 3. Fork this repo as `jupyter-notebook` to your own GitHub account. Note that the name of the repo should match the name of the service, but you can use another name as long as they both match. 9 | 4. Set your Jamsocket account name as the `JAMSOCKET_ACCOUNT` value in your GitHub secrets. 10 | 5. Generate an access token at [app.jamsocket.com/settings](https://app.jamsocket.com/settings) and set it as the `JAMSOCKET_TOKEN` value in your GitHub secrets. 11 | 6. Push your repo to your GitHub account, and your Jupyter Notebook container will be automatically built and pushed to the Jamsocket service you selected. 12 | 7. [Use the Jamsocket API](https://docs.jamsocket.com/platform/advanced/connection-url) to spawn instances of your notebook. It will return a URL that you can use to access the notebook. 13 | 14 | ## Customizing 15 | 16 | You can customize the initial notebook(s) and Python packages as follows: 17 | 18 | 1. Add your notebook(s) to the `/notebook` directory. 19 | 2. Add your notebook's python dependencies to `requirements.txt` (alternatively, if you prefer `pipenv`, add them with `pipenv install` and then run `pipenv run freeze`). 20 | 3. Replace `/notebooks/hello-jamsocket.ipynb` in `Dockerfile` with the path to the notebook you'd like to be automatically loaded. If you want the user to see a file listing of the `/notebooks` directory by default, remove that line entirely. 21 | 4. Run `./freeze.sh` to create a `freeze.txt` file from `requirements.txt`. This pins the version of all of the dependencies of the libraries in `requirements.txt`. 22 | 23 | 24 | ## To run the jupyter-notebook locally for testing: 25 | 26 | ```bash 27 | docker build -t jupyter-notebook . 28 | docker run -p 8080:8080 --env PORT=8080 -it jupyter-notebook 29 | open localhost:8080 30 | ``` 31 | 32 | ## Jupyter Lab 33 | 34 | By default, this runs the `jupyter notebook` command. If you want to run the `jupyter lab` command instead, you can set the `JUPYTER_SUBCOMMAND` environment variable to `lab`. 35 | --------------------------------------------------------------------------------