Deploy JupyterHub with the jupyterhub template
Deploy JupyterHub with the jupyterhub template
Stand up JupyterHub, a multi-user notebook server, on a single Quake AI instance using the validated OpenTofu template jupyterhub. You apply the template, reach the hub over the floating IP, register the admin account, spawn a CPU-only scipy notebook, and put a reverse proxy in front so the hub runs over HTTPS.
JupyterHub is the analyst and data-scientist front door for notebooks on the platform. You run it yourself; this is a self-hosted tool you operate, not a managed service.
Monthly cost estimate
Pricing calculator ↗Sized as a custom package on shared vCPU.
Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.
What each resource is for
JupyterHub host
s1a.medium · 4 shared vCPU, 4 GiB RAM, 0.5 Gbps
Runs JupyterHub in Docker with DockerSpawner, spawning CPU-only scipy notebook containers per user. Hub state and notebook volumes live on an attached block volume.
JupyterHub plus one concurrent scipy notebook runs on 4 vCPU and 4 GiB RAM (s1a.medium). Size up as more users run notebooks at the same time.
Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.
Included in baseline
s1a.medium
4 shared vCPU, 4 GiB RAM, 0.5 Gbps
Compute + RAM rate basis
4 vCPU + 4 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.
Block storage (60 GiB)
60 GiB at $0.08/GiB/mo
Public IP (included)
1 included with the custom package
Package promotional discount
Flat −$5.00/mo on the custom package (same promotion as named plans).
Included at no charge
These line items are zero on Quake AI. Many other providers meter them separately.
Data transfer (inbound and outbound)
Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.
AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.
Learn morePrivate networking
Private networks, subnets, Neutron routers, and security groups are included with the plan.
VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.
Control-plane API requests
OpenStack API calls for provisioning and management are included.
Some managed services on other clouds meter API calls or charge for premium control-plane features.
Pricing data last validated: . For current rates, check quake.ai/pricing.
Prerequisites#
You need:
- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (
source openrc.sh). See the OpenStack CLI guide. - An SSH keypair that already exists in your project. Record its name for the
key_namevariable. - A copy of the
jupyterhubtemplate directory from the template reference page. - Your workstation's public IP address, so you can open the hub port to it for first-boot setup. Find it with
curl -sS https://api.ipify.org.
A domain is optional for first boot. You add it in step 4 to serve the hub over HTTPS.
Step 1: Set the variables and apply the template#
The hub listens on port 8000 over plain HTTP. The template's security group restricts port 8000 to hub_allowed_cidr, which defaults to the private network only, so the raw hub stays off the public internet. To reach the hub from your workstation for first-boot setup, set hub_allowed_cidr to your own address.
Copy the template's example variables file and open it:
cp terraform.tfvars.example terraform.tfvarsSet key_name to the SSH keypair already in your project, and hub_allowed_cidr to your workstation's public IP with a /32 suffix:
key_name = "YOUR_KEY_NAME"
hub_allowed_cidr = "YOUR_IP/32"Initialize the working directory, preview the plan, and apply:
tofu init
tofu plan
tofu applyOpenTofu provisions a private network, a router, a security group, a block volume mounted at /var/lib/docker, an instance, and a floating IP. On first boot, cloud-init mounts the data volume, installs Docker Engine, builds the JupyterHub image, and starts the hub on port 8000.
When the apply finishes, read the outputs:
tofu outputRecord floating_ip and hub_url.
Step 2: Register the admin account#
JupyterHub does not ship a default password. You register the admin account through the signup page on first visit.
cloud-init takes several minutes after the instance reaches ACTIVE (Docker image build included). Open hub_url (for example http://YOUR_FLOATING_IP:8000) in your browser. If the page does not load yet, wait and retry; you can watch the hub container start over SSH:
ssh ubuntu@YOUR_FLOATING_IP "sudo docker ps --filter name=jupyterhub"When the signup page appears, enter a username, email, and strong password. This account administers the hub. JupyterHub signs you in and opens the control panel where you can spawn a notebook server.
Step 3: Spawn a notebook and run code#
From the JupyterHub control panel, select Start My Server. JupyterHub pulls the scipy notebook image (if not already cached) and starts a CPU-only notebook container for your user.
When the server is ready, JupyterHub opens JupyterLab. Create a new Python notebook and run a quick check:
import numpy as np
import pandas as pd
df = pd.DataFrame({"x": np.arange(5), "y": np.arange(5) ** 2})
dfConfirm the cell returns a small table. Your notebooks and files persist in a Docker volume on the data volume, so they survive hub restarts.
Step 4: Serve the hub over HTTPS with Caddy#
The template leaves ports 80 and 443 open for a reverse proxy. Caddy
- Create a DNS A record for your domain (for example
notebooks.example.com) pointing atYOUR_FLOATING_IP. Follow How to point a domain at a Quake AI resource. Wait until the record resolves:
dig +short notebooks.example.comThe command returns your floating IP once the record propagates.
- SSH to the instance and add a Caddy service that proxies HTTPS to JupyterHub on port 8000. Create
/opt/jupyterhub/Caddyfile:
notebooks.example.com {
reverse_proxy 127.0.0.1:8000
}- Add Caddy to the compose file at
/opt/jupyterhub/docker-compose.ymlso it runs alongside JupyterHub:
services:
caddy:
image: caddy:2
restart: unless-stopped
network_mode: host
volumes:
- /opt/jupyterhub/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
volumes:
caddy_data:- Apply the changes and confirm both containers run:
cd /opt/jupyterhub
sudo docker compose up -d
sudo docker compose psOpen https://notebooks.example.com and confirm the padlock. For background on certificate issuance and renewal, see How to issue and auto-renew a TLS certificate with Let's Encrypt. Once HTTPS works, close direct access to port 8000 by setting hub_allowed_cidr back to the private network in terraform.tfvars and running tofu apply.
What you built#
- Applied the
jupyterhubtemplate to provision a network, security group, data volume, instance, and floating IP, and let cloud-init install Docker and start JupyterHub - Registered the admin account on the hub's signup page
- Spawned a CPU-only scipy notebook and ran Python code in JupyterLab
- Served the hub over HTTPS by pointing a domain at the floating IP and routing it through a Caddy reverse proxy
Scope of this deployment#
This template runs a single-VM JupyterHub host, not a managed notebook cloud. The instance is CPU-only and runs in one region. You operate the instance, Docker, JupyterHub, and the data volume yourself: back them up, patch them, and watch resource use as more users spawn notebooks concurrently. For GPU training or large-scale distributed notebooks, use an external GPU backend; this template does not provision GPUs.
Next steps#
- JupyterHub template: the template reference, parameters, and resource map
- self-managed PostgreSQL template: a warehouse to query from notebooks
- Deploy an inference gateway with OpenTofu: GPU inference and RAG behind an OpenAI-compatible endpoint
- How to store application secrets and inject them at runtime: move database and API credentials out of notebook cells
- Security hardening checklist: tighten SSH access and exposure before you serve real traffic
Clean up#
When you no longer need the deployment, destroy everything the template created:
tofu destroyThen remove the DNS A record you created in step 4. Because JupyterHub, user notebooks, and the hub database all live on the instance and its attached volume, tofu destroy removes them along with the infrastructure. Export any notebooks you want to keep before you destroy.
Quick answers
- Why does `openstack image save` write a 0-byte file for my boot-from-volume instance?CLI
- Why does `openstack server create` fail with "Only volume-backed servers are allowed for flavors with zero disk"?CLIAPITerraform
- Why does my project still have a 10 GiB Cinder volume after I deleted my instance?CLIAPI
See Also
Instances
Prerequisite
Migrate a Docker container app from AWS to Quake AI
Shares: Docker, Containers
Deploy Airbyte with the airbyte template
Shares: Docker, Containers
Deploy Airflow with the airflow template
Shares: Docker, Containers
Deploy Umami with the analytics-umami template
Shares: Docker, Containers