Run a Jupyter notebook in a Modal Sandbox
This example demonstrates how to run a Jupyter notebook in a Modal Sandbox.
Setting up the Sandbox
All Sandboxes are associated with an App.
We look up our app by name, creating it if it doesn’t exist.
import json
import secrets
import time
import urllib.request
import modal
app = modal.App.lookup("example-jupyter", create_if_missing=True)
We define a custom Docker image that has Jupyter and some other dependencies installed. Using a pre-defined image allows us to avoid re-installing packages on every Sandbox startup.
image = (
modal.Image.debian_slim(python_version="3.12").pip_install("jupyter~=1.1.0")
# .pip_install("pandas", "numpy", "seaborn") # Any other deps
)
Starting a Jupyter server in a Sandbox
Since we’ll be exposing a Jupyter server over the Internet, we need to create a password.
We’ll use secrets
from the standard library to create a token
and then store it in a Modal Secret.
token = secrets.token_urlsafe(13)
token_secret = modal.Secret.from_dict({"JUPYTER_TOKEN": token})
Now, we can start our Sandbox. Note our use of the encrypted_ports
argument, which
allows us to securely expose the Jupyter server to the public Internet. We use
modal.enable_output()
to print the Sandbox’s image build logs to the console.
JUPYTER_PORT = 8888
print("🏖️ Creating sandbox")
with modal.enable_output():
sandbox = modal.Sandbox.create(
"jupyter",
"notebook",
"--no-browser",
"--allow-root",
"--ip=0.0.0.0",
f"--port={JUPYTER_PORT}",
"--NotebookApp.allow_origin='*'",
"--NotebookApp.allow_remote_access=1",
encrypted_ports=[JUPYTER_PORT],
secrets=[token_secret],
timeout=5 * 60, # 5 minutes
image=image,
app=app,
gpu=None, # add a GPU if you need it!
)
print(f"🏖️ Sandbox ID: {sandbox.object_id}")
Communicating with a Jupyter server
Next, we print out a URL that we can use to connect to our Jupyter server.
Note that we have to call Sandbox.tunnels
to get the URL. The Sandbox is not publicly accessible until we do so.
tunnel = sandbox.tunnels()[JUPYTER_PORT]
url = f"{tunnel.url}/?token={token}"
print(f"🏖️ Jupyter notebook is running at: {url}")
Jupyter servers expose a REST API that you can use for programmatic manipulation.
For example, we can check the server’s status by
sending a GET request to the /api/status
endpoint.
def is_jupyter_up():
try:
response = urllib.request.urlopen(
f"{tunnel.url}/api/status?token={token}"
)
if response.getcode() == 200:
data = json.loads(response.read().decode())
return data.get("started", False)
except Exception:
return False
return False
We’ll now wait for the Jupyter server to be ready by hitting that endpoint.
timeout = 60 # seconds
start_time = time.time()
while time.time() - start_time < timeout:
if is_jupyter_up():
print("🏖️ Jupyter is up and running!")
break
time.sleep(1)
else:
print("🏖️ Timed out waiting for Jupyter to start.")
You can now open this URL in your browser to access the Jupyter notebook!
When you’re done, terminate the sandbox using your Modal dashboard
or by running Sandbox.from_id(sandbox.object_id).terminate()
.