Jupyter Deploy AWS EKS OIDC template
The AWS EKS OIDC Template deploys JupyterLab workspaces on an Amazon EKS cluster, with HTTPS, GitHub OAuth via Dex, and the Jupyter K8s operator for workspace lifecycle management.
Multiple users get isolated workspaces with persistent storage, idle shutdown,
and role-based access control — all managed through jupyter-deploy.
Documentation: jupyter-deploy.readthedocs.io
The AWS EKS OIDC Template is maintained and supported by AWS.
10k View
When a user navigates to the deployment URL, they authenticate via GitHub through a Dex OIDC flow. Once authenticated, the Jupyter K8s operator provisions a workspace pod on the cluster with a dedicated persistent volume. Traefik routes HTTPS traffic to the workspace, and an authmiddleware component validates JWT session cookies on every request.
Administrators use jupyter-deploy to monitor cluster health, manage workspaces, and inspect platform components — without needing direct Kubernetes access.
Prerequisites
AWS account
The template creates AWS resources (VPC, EKS cluster, node groups, IAM roles, ECR, Route 53 records). Your local environment needs access to valid AWS credentials.
If you do not have an AWS account, follow the official guide to create one.
If you already have an AWS account, make sure your CLI credentials are configured.
Get and register a domain
The template serves workspace URLs under a subdomain of your own domain. You will need to specify this domain when you configure the project.
If you already own a domain, register it with Amazon Route 53 using this guide.
If you do not own a domain yet, you can buy one through Amazon Route 53 using this guide.
Setup a GitHub OAuth app
The template gates access to workspaces with GitHub identities via Dex as the OIDC provider. You will need to create a GitHub OAuth app for this purpose.
First, log on to GitHub on your web browser, then use this link to create a new OAuth app.
You can choose any name for the application; for example jupyter-deploy-eks-oidc.
Set Homepage URL to: https://<subdomain>.<domain>; for example https://workspaces.example.com.
<domain> corresponds to the domain above. You can choose any <subdomain> you like, but it must be a valid domain part (lowercase letters, digits, or hyphens).
Set Authorization callback URL to: https://<subdomain>.<domain>/dex/callback.
Make sure it matches the <domain> and <subdomain> exactly.
Note: Make sure the callback URL ends in
/dex/callback
In the next page, write down and save your app client ID. Generate an app client secret, and write it down as well.
The oauth_allowed_teams variable grants access to GitHub teams, in org:team format. To grant an organization's teams access, create the OAuth app under that organization (Settings → Developer settings → OAuth Apps) so its owners can manage the app and the app can read team membership. See Prerequisites for details.
Refer to GitHub documentation for more details.
Usage
Installation
Recommended: create or activate a Python virtual environment.
uv add "jupyter-deploy[aws,k8s]" jupyter-deploy-tf-aws-eks-oidc
Or with pip:
pip install "jupyter-deploy[aws,k8s]" jupyter-deploy-tf-aws-eks-oidc
Project setup
mkdir my-eks-deployment
cd my-eks-deployment
jd init . -E terraform -P aws -I eks -T oidc
Consider making my-eks-deployment a git repository.
Configure and deploy
jd config
jd up
The first deployment takes approximately 25-30 minutes.
User interface
# open the web UI
jd open
# configure kubectl to access your cluster
jd cluster login
Manage user workspaces
# list all workspaces
jd server list
# check workspace status
jd server status --name my-workspace --scope default
# open a running workspace in your web browser
jd open --server-name my-workspace --scope default
# stop/start a workspace
jd server stop --name my-workspace --scope default
jd server start --name my-workspace --scope default
# view workspace logs
jd server logs --name my-workspace --scope default
# open an interactive shell
jd server connect --name my-workspace --scope default
Manage cluster
# aggregated health check
jd health
# cluster control plane status
jd cluster status
# configure local kubeconfig
jd cluster login
Manage components
# health dashboard for all platform components
jd health --components
# list the platform components
jd component list
# view component logs
jd component logs --name dex
# restart a component (deployment only)
jd component restart --name oauth2-proxy
# reconcile a component (HelmRelease only; re-asserts desired state)
jd component reconcile --name workspace-router-chart
Take down
This operation removes all the resources associated with this project in your AWS account.
jd down
Architecture
Cluster and networking
The template creates a VPC with public and private subnets across two availability zones. EKS nodes run in private subnets; the Network Load Balancer (NLB) sits in public subnets and forwards HTTPS/443 to Traefik pods. Amazon Route 53 manages DNS via external-dns. Public TLS terminates at the NLB with an ACM certificate and is re-encrypted to Traefik, which presents a certificate from a private CA that cert-manager mints in the cluster.
Platform components
The platform layer handles routing, authentication, and workspace lifecycle:
- Traefik — Ingress controller and reverse proxy
- Dex — OIDC identity provider (federates to GitHub)
- OAuth2 Proxy — Handles sign-in flow for unauthenticated users
- Authmiddleware — Validates JWT session cookies via ForwardAuth
- Jupyter K8s operator — Reconciles Workspace CRDs into Deployments and persistent volumes
Authentication flow
Authentication uses a two-path model:
Browser access — OAuth2 Proxy redirects unauthenticated requests through Dex to GitHub. After authentication, Authmiddleware issues a JWT session cookie. Authmiddleware validates subsequent requests by checking the JWT directly.
CLI access — jd cluster login configures kubeconfig with an EKS exec-based auth provider using AWS IAM credentials, bypassing the OIDC flow entirely.
Compute
Three kinds of node pool, each dedicated to a different job and each with its own autoscaling policy:
- platform — EKS managed node group for the control-plane pods (operator, Karpenter, KEDA, cert-manager, external-dns); the Cluster Autoscaler sizes it
- routing — Karpenter pool for the ingress and auth tier (Traefik, Dex, OAuth2 Proxy, Authmiddleware, web UI); always on, and grows with the KEDA-scaled routing pods
- workspace pools — Karpenter pools for user workspace pods, one per
workspace_nodepoolsentry plus an optional GPU pool; these scale to zero
A single jupyter-deploy/role Kubernetes label drives pod placement: every node carries it, and every pod selects its tier through a matching nodeSelector. The Karpenter pools carry the role as a taint as well, so a pod without the matching toleration can never land on them; the platform node group stays untainted. The platform node group auto-detects its AMI type from the instance family (CPU, GPU, Neuron; x86_64 or arm64).
Storage
Each workspace gets a dedicated EBS persistent volume mounted at /home/jovyan. Data persists across workspace stop/start cycles and pod restarts.
Custom workspace images
The template optionally creates ECR repositories and a CodeBuild project for building custom workspace images from Dockerfiles in the applications/ directory.
Details
Networking
The template creates a VPC with public and private subnets across two availability zones. EKS nodes run in private subnets; the Network Load Balancer (NLB) sits in public subnets and forwards HTTPS/443 to Traefik pods.
Amazon Route 53 manages DNS. The template references a Hosted Zone for your domain (which must already exist) and relies on external-dns to create DNS records pointing your subdomain to the NLB.
TLS
Public TLS terminates at the NLB with an AWS Certificate Manager (ACM) certificate that the template requests for <subdomain>.<domain> and validates via DNS against your hosted zone.
The NLB then re-encrypts to Traefik, so nothing crosses the VPC in plaintext. Traefik presents a certificate from a private CA that cert-manager mints inside the cluster; the NLB does not verify it, so it needs no public trust. cert-manager therefore makes no AWS API calls and holds no IAM role.
The NLB registers only nodes labeled jupyter-deploy/role=routing as targets, matching where Traefik runs.
Compute
The template creates a single platform EKS managed node group for control-plane-only pods, and Karpenter NodePools for the routing and workspace tiers. The managed node group auto-detects the appropriate EKS-optimized AMI type for its instance type; Karpenter nodes use the al2023@latest AMI alias.
Application Images
The template creates infrastructure for building custom workspace images:
- ECR repository — One repository per application type. Stores the built container images.
- CodeBuild project — Builds Dockerfiles from the
applications/directory and pushes to ECR. - IAM role — CodeBuild service role with permissions for ECR push and CloudWatch Logs.
IAM
The template creates several IAM roles:
- Cluster role — The EKS control plane assumes this role
- Node roles — One per node group, with managed policies for ECR pull, EKS worker nodes, and CNI
- Pod identity associations — external-dns uses EKS Pod Identity for Route 53 access
- Admin access entries — The template always authorizes the caller's IAM principal. Roles in
admin_role_namesand users inadmin_user_namesget cluster admin and workspace admin permissions. For stable state across caller switches, list every role/user that may runjd configorjd up
Helm charts
| Chart | Namespace | Purpose |
|---|---|---|
| traefik-crds | Router namespace | Traefik CRDs (IngressRoute, Middleware, etc.) |
| jupyter-k8s | Operator namespace | Workspace operator, extension API server, CRDs |
| jupyter-k8s-aws-oidc | Router namespace | Traefik, Dex, OAuth2 Proxy, Authmiddleware, web UI |
| karpenter | karpenter |
Karpenter node-autoscaler controller |
| karpenter-nodepools (local) | karpenter |
Karpenter NodePool + EC2NodeClass definitions (routing, workspaces) |
| keda | keda |
Event-driven autoscaling for the routing tier |
| cluster-autoscaler | kube-system |
Autoscaler for the platform managed node group |
| prometheus | monitoring |
Metrics server (scaling source for KEDA) |
| aws-for-fluent-bit | kube-system |
Pod log shipping to CloudWatch (optional, enable_component_logging) |
| nvidia-device-plugin | kube-system |
Registers GPU capacity on GPU pool nodes (optional; a pool entry setting accelerator = "nvidia" pulls it in) |
| github-rbac (local) | Shared namespace | Namespace-scoped RBAC for the oauth_allowed_teams GitHub teams |
| workspace-defaults (local) | Shared namespace | Default WorkspaceTemplate and workspace-ingress NetworkPolicies |
RBAC
The template deploys a github-rbac local chart that creates namespace-scoped Role and RoleBinding resources. Each namespace in workspace_rbac_namespaces gets a Role granting workspace CRUD permissions, which a RoleBinding ties to the GitHub teams in oauth_allowed_teams. Those teams also get a read-only (get/list) Role in workspace_shared_namespace for discovering shared WorkspaceTemplate and WorkspaceAccessStrategy resources.
Presets
The template provides two variable presets:
defaults-all.tfvars— comprehensive preset with all recommended valuesdefaults-base.tfvars— minimal preset that additionally prompts for node group configuration
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| cluster_name_prefix | string |
jupyter-deploy-eks |
Prefix for the EKS cluster name (template appends a unique suffix) |
| region | string |
us-west-2 |
AWS region where to deploy the resources |
| kubernetes_version | string |
1.36 |
Kubernetes version for the EKS cluster |
| domain | string |
Required | Domain name for workspace URLs (must have a Route 53 hosted zone) |
| subdomain | string |
Required | Subdomain prefix for workspace URLs |
| oauth_app_client_id | string |
Required | Client ID of the GitHub OAuth app |
| oauth_app_client_secret | string |
Required | Client secret of the GitHub OAuth app |
| oauth_allowed_teams | list(string) |
Required | GitHub teams to allow access, in org:team format |
| platform_instance_types | list(string) |
["m5.large"] |
Instance types for the platform managed node group |
| platform_disk_size_gb | number |
50 |
Root volume size for platform nodes |
| platform_min_size | number |
2 |
Minimum size of the platform node group (one node per AZ) |
| platform_max_size | number |
3 |
Maximum size of the platform node group |
| routing_instance_categories | list(string) |
["c", "m"] |
Instance categories Karpenter may pick for routing nodes |
| routing_instance_generation_min | string |
6 |
Minimum instance generation for routing nodes |
| routing_disk_size_gb | number |
50 |
Root volume size for routing nodes |
| routing_max_cpu | string |
32 |
Ceiling on total vCPU of the routing pool |
| routing_max_memory | string |
128Gi |
Ceiling on total memory of the routing pool |
| workspace_nodepools | list(map(string)) |
one workspace-cpu pool |
Karpenter workspace pools, each with its own instance families and CPU/memory ceilings |
| node_expire_after | string |
504h |
Maximum node lifetime before Karpenter recycles it |
| workspace_rbac_namespaces | list(string) |
["default"] |
Namespaces where teams get workspace permissions |
| admin_role_names | list(string) |
[] |
IAM role names to grant cluster and workspace admin (list all callers for stable state) |
| admin_user_names | list(string) |
[] |
IAM user names to grant cluster and workspace admin (list all callers for stable state) |
| cluster_log_retention_days | number |
30 |
Days to retain EKS cluster CloudWatch logs |
| custom_tags | map(string) |
{} |
Tags added to all AWS resources |
| workspace_operator_namespace | string |
jupyter-k8s-system |
Namespace for the workspace operator |
| workspace_router_namespace | string |
jupyter-k8s-router |
Namespace for routing components |
| workspace_shared_namespace | string |
jupyter-k8s-shared |
Namespace for shared workspace resources |
| workspace_operator_chart_oci | string |
See preset | OCI reference for the jupyter-k8s Helm chart |
| workspace_operator_chart_version | string |
See preset | Version of the jupyter-k8s chart |
| workspace_router_chart_oci | string |
See preset | OCI reference for the aws-oidc router chart |
| workspace_router_chart_version | string |
See preset | Version of the aws-oidc chart |
| traefik_crd_chart_version | string |
1.15.0 |
Version of the Traefik CRDs chart |
| workspaces_default_access_type | string |
OwnerOnly |
Default access type for new workspaces (OwnerOnly or Public) |
| workspaces_default_ownership_type | string |
OwnerOnly |
Default ownership type for new workspaces |
| workspaces_idle_shutdown_enabled | bool |
true |
Enable automatic idle shutdown for workspaces |
| workspaces_idle_shutdown_timeout_default | number |
60 |
Default idle timeout in minutes (5-1440) |
| workspaces_idle_shutdown_timeout_min | number |
15 |
Minimum idle timeout users can set (advanced; lower values mainly serve testing) |
| workspaces_idle_shutdown_timeout_max | number |
480 |
Maximum idle timeout users can set (15-1440) |
| workspace_app_jupyterlab_use | bool |
true |
Build and deploy the JupyterLab workspace image |
| workspace_app_jupyterlab_app_type | string |
jupyterlab |
Application type identifier for the workspace template |
| workspace_app_jupyterlab_image_name | string |
See preset | ECR repository name for the JupyterLab image |
| workspace_app_jupyterlab_image_build | string |
v1 |
Build tag (increment to trigger rebuild) |
| workspace_templates | list(map(string)) |
[] |
Named workspace template configs that pool entries offer as cards via their templates key |
| enable_default_gpu_pool | bool |
false |
Append the built-in workspace-gpu entry and its jupyterlab-gpu template config; combining it with your own accelerator entries raises a plan-time error |
| nvidia_device_plugin_version | string |
0.20.1 |
Version of the NVIDIA device plugin chart (the template installs it when any pool entry sets accelerator = "nvidia") |
Outputs
| Name | Description |
|---|---|
cluster_name |
Name of the EKS cluster |
cluster_endpoint |
API server endpoint URL for the EKS cluster |
platform_mng_names |
Names of the EKS managed node groups |
cluster_arn |
ARN of the EKS cluster |
cluster_ca_certificate |
Base64-encoded CA certificate for the EKS cluster |
region |
AWS region hosting the cluster |
deployment_id |
Unique deployment identifier |
vpc_id |
ID of the VPC hosting the EKS cluster |
workspace_operator_namespace |
Kubernetes namespace for the workspace operator controller |
workspace_router_namespace |
Kubernetes namespace for routing components |
workspace_shared_namespace |
Kubernetes namespace for shared workspace resources |
workspace_base_url |
Base URL for workspace access |
get_started_url |
URL to the web UI |
secret_arn |
ARN of the Secrets Manager secret storing the OAuth app client secret |
jupyterlab_image_uri |
ECR image URI for the JupyterLab workspace image |
kubeconfig_path |
Path to the local kubeconfig file for this cluster |
acm_certificate_arn |
ARN of the ACM certificate for the deployment domain |
License
The AWS EKS OIDC Template is licensed under the MIT License.
Metadata
Release files for jupyter-deploy-tf-aws-eks-oidc 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jupyter_deploy_tf_aws_eks_oidc-0.4.0.tar.gz | 154.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jupyter_deploy_tf_aws_eks_oidc-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 271.5 kB
Release files / jupyter_deploy_tf_aws_eks_oidc-0.4.0.tar.gz
| Download URL | jupyter_deploy_tf_aws_eks_oidc-0.4.0.tar.gz |
|---|---|
| Size | 154.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0c372fe2cdf34c1e610867a66494d071ae158798f698720c04408f9658b1055e
|
|
BLAKE2b-256 checksum How to use checksums |
7bc07f60e29ada60c64fa1b54fc96773a20fcb7e518dc8edd9aa15969a302026
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / jupyter_deploy_tf_aws_eks_oidc-0.4.0-py3-none-any.whl
| Download URL | jupyter_deploy_tf_aws_eks_oidc-0.4.0-py3-none-any.whl |
|---|---|
| Size | 117.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f1c9dc3cef6ee6ee73b8ba562d3fc3d5899d7b5218349740ae674172b44fc4ae
|
|
BLAKE2b-256 checksum How to use checksums |
812766a69f13ede887c645e56735780fb7933c25cff04fac8b9186b91fe5212b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|