Positron IDE on stat.datahub.berkeley.edu
Positron is available to users of stat.datahub.berkeley.edu via jupyter-positron-server, a jupyter-server-proxy extension. It requires an academic license from Posit (academic-licenses@posit.co), which is educational-use-only and enforced per-session by a privileged Hub-side “verifier” service.
Architecture
stat-user-image (single-user pod)
jupyter-positron-server -- requests a license from the hub at session
start, passes it to positron-server
positron-server -- verifies the signed license and starts
(no static license file is ever present here)
stat hub pod
hub container -- proxies /services/positron-license/* to the
sidecar; unaware of the secrets below
positron-verifier sidecar -- holds signing-key.pem + license.lic, mints a
fresh signed license per session
Token flow, adapted from the upstream get_started guide:
jupyter-positron-servercallsPOSITRON_LICENSE_MINTING_ENDPOINT(http://proxy-public/services/positron-license/mint), authenticated with itsJUPYTERHUB_API_TOKEN.- CHP (
proxy-public), per the route JupyterHub registered for thepositron-licenseservice, reverse-proxies that request to thepositron-verifiersidecar via the hub Service’spositron-verifierport (hub:10101, fromhub.service.extraPorts). - The sidecar verifies the user, checks entitlement via
license-manager(readinglicense.lic), and returns a signed, session-bound license. jupyter-positron-serverstartspositron-serverwith that license.
Where things live
| Component | Location |
|---|---|
positron-server binary + jupyter-positron-server |
images/stat-user-image (Dockerfile, environment.yml) |
jupyter-positron-verifier sidecar image |
images/positron-verifier/Dockerfile (chartpress-managed) |
Sidecar wiring, hub.services, hub.loadRoles, hub.extraFiles structure |
deployments/stat/config/common.yaml |
Actual license.lic / signing-key.pem contents |
deployments/stat/secrets/{staging,prod}.yaml (sops/GCP-KMS encrypted) |
The single-user image never carries a copy of the license file – only the Hub-pod sidecar does, and only as a mounted secret, never baked into the image.
Rebuilding and deploying the verifier sidecar image
images/positron-verifier is chartpress-managed but, like images/postgres (see Customize the Per-User Postgres Docker Image), it’s only used by one deployment, so chartpress can’t auto-write its tag into any values.yaml.
- Modify
images/positron-verifier/Dockerfile(e.g. to bump the Positron Server release) and make a git commit. - From the
datahubrepo root – the same directory aschartpress.yaml, not from insideimages/positron-verifier– on a PC runchartpress --push; on a Mac runchartpress --push --builder docker-buildx --platform linux/amd64. There’s no per-image flag, so this also builds/pusheshub,postgres, andnode-placeholder-scaler; chartpress normally skips images that already exist unchanged, but if it does rebuild them (e.g. after a fresh checkout with no local Docker cache, or a chartpress version/config mismatch with CI), it will also rewritehub/values.yaml,hub/Chart.yaml,node-placeholder/values.yaml, andnode-placeholder/Chart.yaml– those bump the shared Hub image for every deployment, not juststat. Checkgit status/git diffafterward andgit checkoutthose four files back if you didn’t mean to touch them; the freshly-pushedhub/node-placeholder-scalerimages are harmless left unreferenced in the registry. - Note the
positron-verifierimage name + tag chartpress prints, and put it indeployments/stat/config/common.yamlunderjupyterhub.hub.extraContainers(thepositron-verifierentry’simage:). - Commit the new tag and deploy as normal.
The same tag also needs to exist on first setup – common.yaml ships with us-central1-docker.pkg.dev/ucb-datahub-2018/core/positron-verifier:PLACEHOLDER until step 2 has been run at least once.
Rotating the license or signing key
When Posit issues a new license.lic or signing-key.pem (e.g. renewal, compromise, license upgrade):
sops -d deployments/stat/secrets/prod.yaml > /tmp/prod.dec.yaml
# edit /tmp/prod.dec.yaml: update
# jupyterhub.hub.extraFiles.positron-license-lic.stringData
# jupyterhub.hub.extraFiles.positron-signing-key.stringData
cp /tmp/prod.dec.yaml deployments/stat/secrets/prod.yaml
sops -e -i deployments/stat/secrets/prod.yaml
shred -u /tmp/prod.dec.yaml # or `rm -P`/`rm -f` if shred isn't availableRepeat for staging.yaml. Never leave the decrypted file at the target path without immediately re-running sops -e -i on it, and never commit before that step – git diff should only ever show ENC[...] changes for these files.
Security notes
signing-key.pemnever leaves the Hub pod – it’s mounted only into thepositron-verifiersidecar container, not the single-user image.license.licis likewise only mounted into the sidecar; the minting-endpoint flow means single-user sessions never need a static copy.- Both secrets are sops-encrypted at rest via the same GCP KMS key used for every other deployment’s secrets (
.sops.yaml).