Base terraform template to deploy a JupyterLab application on an AWS EC2 instance.
Project description
Jupyter Deploy AWS EC2 base template
The AWS Base Template deploys a JupyterLab application to a dedicated Amazon EC2 instance, served on your domain with encrypted HTTPS, GitHub OAuth integration, real-time collaboration, and fast UV-based environments.
Documentation: jupyter-deploy.readthedocs.io
The AWS Base Template is maintained and supported by AWS.
10k View
When you navigate to the application URL in your web browser, you connect to the EC2 instance over HTTPS. On the first visit, the instance redirects to GitHub for OAuth authentication; once verified, you connect to the jupyter container within your EC2 instance and see a JupyterLab application in your web browser.
Prerequisites
AWS account
The template needs to create AWS resources. 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 your JupyterLab app to a URL in 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 your JupyterLab application with GitHub identities. 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-aws-base.
Set Homepage URL to: https://<subdomain>.<domain>; for example https://jupyterlab-app.<domain>.
<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>/oauth2/callback.
Make sure it matches the <domain> and <subdomain> exactly.
In the next page, write down and save your app client ID. Generate an app client secret, and write it down as well.
Refer to GitHub documentation for more details.
Usage
This terraform project is meant to be used with the jupyter-deploy CLI.
Installation
Recommended: create or activate a Python virtual environment.
uv add jupyter-deploy[aws] jupyter-deploy-tf-aws-ec2-base
Or with pip:
pip install jupyter-deploy[aws] jupyter-deploy-tf-aws-ec2-base
Project setup
mkdir my-jupyter-deployment
cd my-jupyter-deployment
jd init . -E terraform -P aws -I ec2 -T base
Consider making my-jupyter-deployment a git repository.
Configure and create the infrastructure
jd config
jd up
Access your JupyterLab application
# verify that your host and containers are running
jd host status
jd server status
# open your application on your web browser
jd open
Manage access
# By GitHub users
jd users list
jd users add USERNAME1 USERNAME2
jd users remove USERNAME1
# By GitHub organization
jd organization get
jd organization set ORGANIZATION
jd organization unset
# Along with GitHub organization, by teams
jd teams list
jd teams add TEAM1 TEAM2
jd teams remove TEAM2
Temporarily stop/start your EC2 instance
# To stop your instance
jd host stop
jd host status
# To start it again
jd host start
jd server start
jd server status
Manage your EC2 instance
# connect to your host
jd host connect
# disconnect
exit
Take down all the infrastructure
This operation removes all the resources associated with this project in your AWS account.
jd down
Architecture
Containers
The application runs as a set of containerized services orchestrated by Docker Compose. Traefik acts as the reverse proxy — it handles TLS termination, routes incoming HTTPS requests, and delegates authentication decisions to OAuth2 Proxy via the ForwardAuth middleware. OAuth2 Proxy manages the full OAuth 2.0 flow with GitHub as the identity provider, handling redirects, token validation, and session cookies. Traefik forwards authenticated requests to the JupyterLab container. A Fluent Bit sidecar collects service logs and a logrotate container manages log retention on disk.
Authentication flow
On first visit, Traefik forwards the request to OAuth2 Proxy, which redirects the browser to GitHub for authentication. GitHub prompts the user to authorize the OAuth App, then redirects back to OAuth2 Proxy with an authorization code. OAuth2 Proxy exchanges the code for an access token, verifies the user's identity against the configured allowlist, sets a session cookie, and lets the request through to JupyterLab. The session cookie authenticates subsequent requests without repeating the OAuth dance. See the OAuth2 Proxy GitHub provider documentation for details.
Details
Networking
The template places the EC2 instance in the first subnet of the default VPC in the selected AWS region. The template assigns an Elastic IP (EIP) to the instance to keep its public IP address stable across stop/start cycles.
Amazon Route 53 manages DNS. The template references a Hosted Zone for your domain (which must already exist) and adds a DNS record pointing your subdomain to the instance's Elastic IP.
The instance's security group only allows ingress on port 443 (HTTPS). There is no SSH access — all administrator operations go through AWS Systems Manager (SSM).
Compute
The template selects the latest Amazon Linux 2023 AMI compatible with the chosen instance type:
- Standard AL2023 AMI for CPU instances (x86_64 or arm64)
- Deep Learning AMI (DLAMI) for GPU or Neuron instances (x86_64 or arm64)
You can also provide a specific AMI ID to override automatic selection.
Storage
The instance has two volumes. The root volume inherits its size and settings from the selected AMI, with a configurable minimum size. It persists across instance restarts and instance type changes, as long as the new instance type is compatible with the existing root volume. The template attaches a separate EBS data volume and mounts it into the JupyterLab container at /home/jovyan — this volume persists user data across container restarts and instance stop/start cycles.
You can optionally attach additional EBS volumes or EFS file systems and mount them into the Jupyter home directory.
TLS
Let's Encrypt provides TLS certificates using the ACME protocol. Traefik acts as the ACME client and proves domain ownership via a DNS-01 challenge: it creates a temporary TXT record in the Route 53 Hosted Zone, Let's Encrypt verifies it, and issues the certificate. Traefik stores the certificate in acme.json and renews it automatically. The template also backs the certificate up to AWS Secrets Manager so it persists across instance replacements.
IAM and Secrets
The template creates an IAM role for the EC2 instance with permissions for SSM, Route 53, S3, and (optionally) EFS access.
The template creates two AWS Secrets Manager secrets:
- One to store the OAuth App client secret
- One to store TLS certificates from Let's Encrypt, ensuring they persist across instance replacements
Deployment Configuration
An S3 bucket stores all deployment configuration files: bash scripts, Docker service definitions, and application configuration. The instance pulls these files during setup or updates via a cloud-init script.
The template creates an SSM startup document that orchestrates instance configuration using these files:
| File | Purpose |
|---|---|
cloudinit.sh.tftpl |
EC2 instance configuration |
docker-compose.yml.tftpl |
Docker service definitions |
docker-startup.sh.tftpl |
Docker service startup |
cloudinit-volumes.sh.tftpl |
Optional EBS/EFS volume mounts |
traefik.yml.tftpl |
Traefik reverse proxy configuration |
dockerfile.jupyter |
Jupyter container image |
jupyter-start.sh |
Jupyter container entrypoint |
jupyter-reset.sh |
Fallback if Jupyter fails to start |
pyproject.jupyter.toml |
Python dependencies for the Jupyter environment |
jupyter_server_config.py |
Jupyter server settings |
dockerfile.logrotator |
Log rotation sidecar container |
logrotator-start.sh.tftpl |
Logrotate configuration |
fluent-bit.conf |
Fluent-bit log collection configuration |
parsers.conf |
Fluent-bit Docker log parsers |
If you selected pixi as the dependency manager, the template uses pixi.jupyter.toml instead of pyproject.jupyter.toml.
An SSM association triggers the startup script on the instance whenever the configuration changes.
Operations
The template creates SSM documents that the jd CLI uses to manage the deployment remotely:
| Document | Purpose |
|---|---|
check-status-internal.sh |
Verify services are running and TLS certificates are available |
get-status.sh |
Translate status checks to human-readable output |
update-auth.sh |
Update authorized org, teams, and/or users |
get-auth.sh |
Retrieve current authorization settings |
update-server.sh |
Update running services |
refresh-oauth-cookie.sh |
Rotate the OAuth cookie secret and invalidate all sessions |
Logging
Fluent-bit collects Docker service logs and writes them to /var/log/services on the instance volume. A logrotate sidecar container handles automatic rotation of all log files based on configurable size and retention settings.
Presets
The template provides two variable presets:
defaults-all.tfvars— comprehensive preset with all recommended valuesdefaults-base.tfvars— minimal preset that prompts for instance type and volume size
Requirements
| Name | Version |
|---|---|
| terraform | >= 1.0 |
| aws | >= 4.66 |
Providers
| Name | Version |
|---|---|
| aws | >= 4.66 |
Modules
| Name | Location |
|---|---|
ami_al2023 |
template/engine/modules/ami_al2023 |
certs_secret |
template/engine/modules/certs_secret |
ec2_iam_role |
template/engine/modules/ec2_iam_role |
ec2_instance |
template/engine/modules/ec2_instance |
network |
template/engine/modules/network |
s3_bucket |
template/engine/modules/s3_bucket |
secret |
template/engine/modules/secret |
volumes |
template/engine/modules/volumes |
Resources
| Name | Type |
|---|---|
| aws_security_group | resource |
| aws_instance | resource |
| aws_iam_role | resource |
| aws_iam_role_policy_attachment | resource |
| aws_iam_instance_profile | resource |
| aws_ebs_volume | resource |
| aws_volume_attachment | resource |
| aws_ssm_document | resource |
| aws_ssm_association | resource |
| aws_route53_zone | resource |
| aws_route53_record | resource |
| aws_secretsmanager_secret | resource |
| aws_iam_policy | resource |
| aws_ssm_parameter | resource |
| null_resource | resource |
| aws_default_vpc | resource |
| aws_ebs_volume | resource |
| aws_efs_file_system | resource |
| aws_efs_mount_target | resource |
| aws_eip | resource |
| aws_s3_bucket | resource |
| aws_s3_bucket_versioning | resource |
| aws_s3_bucket_server_side_encryption_configuration | resource |
| aws_s3_bucket_public_access_block | resource |
| aws_s3_object | resource |
| aws_subnets | data source |
| aws_subnet | data source |
| aws_ami | data source |
| aws_route53_zone | data source |
| aws_ebs_volume | data source |
| aws_efs_file_system | data source |
| aws_iam_policy | data source |
| aws_iam_policy_document | data source |
| local_file | data source |
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| region | string |
us-west-2 |
The AWS region where to create the resources |
| instance_type | string |
t3.medium |
The type of instance to start |
| key_pair_name | string |
null |
The name of key pair |
| ami_id | string |
null |
The ID of the AMI to use for the instance |
| min_root_volume_size_gb | number |
30 |
The minimum size in gigabytes of the root EBS volume for the EC2 instance (will use AMI snapshot size if larger) |
| volume_size_gb | number |
30 |
The size in GB of the EBS volume the Jupyter Server has access to |
| volume_type | string |
gp3 |
The type of EBS volume the Jupyter Server will has access to |
| iam_role_prefix | string |
Jupyter-deploy-ec2-base |
The prefix for the name of the IAM role for the instance |
| oauth_app_secret_prefix | string |
Jupyter-deploy-ec2-base |
The prefix for the name of the AWS secret to store your OAuth app client secret |
| s3_bucket_prefix | string |
jupyter-deploy-ec2-base |
The prefix for the name of the S3 bucket where deployment scripts are stored (3-28 characters, lowercase alphanumeric with hyphens) |
| certs_secret_prefix | string |
Jupyter-deploy-ec2-base |
The prefix for the name of the AWS secret where ACME certificates are stored |
| letsencrypt_email | string |
Required | An email for letsencrypt to notify about certificate expirations |
| domain | string |
Required | A domain that you own |
| subdomain | string |
Required | A sub-domain of domain to add DNS records |
| oauth_provider | string |
github |
The OAuth provider to use |
| oauth_allowed_org | string |
"" |
The GitHub organization to allowlist |
| oauth_allowed_teams | list(string) |
[] |
The list of GitHub teams to allowlist |
| oauth_allowed_usernames | list(string) |
[] |
The list of GitHub usernames to allowlist |
| oauth_app_client_id | string |
Required | The client ID of the OAuth app |
| oauth_app_client_secret | string |
Required | The client secret of the OAuth app |
| log_files_rotation_size_mb | number |
50 |
The size in megabytes at which to rotate log files |
| log_files_retention_count | number |
10 |
The maximum number of rotated log files to retain for a log group |
| log_files_retention_days | number |
180 |
The maximum number of days to retain any log files |
| custom_tags | map(string) |
{} |
The custom tags to add to all the resources |
| additional_ebs_mounts | list(map(string)) |
[] |
Elastic block stores to mount on the notebook home directory |
| additional_efs_mounts | list(map(string)) |
[] |
Elastic file systems to mount on the notebook home directory |
Outputs
| Name | Description |
|---|---|
jupyter_url |
The URL to access your notebook app |
auth_url |
The URL for the OAuth callback - do not use directly |
instance_id |
The ID of the EC2 instance |
ami_id |
The Amazon Machine Image ID used by the EC2 instance |
jupyter_server_public_ip |
The public IP assigned to the EC2 instance |
secret_arn |
The ARN of the AWS Secret storing the OAuth client secret |
certs_secret_arn |
The ARN of the AWS Secret where TLS certificates are stored |
deployment_scripts_bucket_name |
Name of the S3 bucket where deployment scripts and service configuration files are stored |
deployment_scripts_bucket_arn |
ARN of the S3 bucket where deployment scripts and service configuration files are stored |
region |
The AWS region where the resources were created |
deployment_id |
Unique identifier for this deployment |
images_build_hash |
Hash of files affecting docker compose image builds (jupyter, log-rotator) |
scripts_files_hash |
Hash of all deployment script files which controls SSM association re-execution |
server_status_check_document |
Name of the SSM document to verify if the server is ready to serve traffic |
server_update_document |
Name of the SSM document to control server container operations |
server_logs_document |
Name of the SSM document to print server logs to terminal |
server_exec_document |
Name of the SSM document to execute commands inside server containers |
server_connect_document |
Name of the SSM document to start interactive shell sessions inside server containers (jupyter or traefik) |
auth_org_unset_document |
Name of the SSM document to remove the allowlisted organization |
auth_check_document |
Name of the SSM document to view authorized users, teams and organization |
auth_users_update_document |
Name of the SSM document to change the authorized users |
auth_teams_update_document |
Name of the SSM document to change the authorized teams |
auth_org_set_document |
Name of the SSM document to allowlist an organization |
auth_org_unset_document |
Name of the SSM document to remove the allowlisted organization |
persisting_resources |
List of identifiers of resources that should not be destroyed |
License
The AWS Base Template is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jupyter_deploy_tf_aws_ec2_base-0.5.1.tar.gz.
File metadata
- Download URL: jupyter_deploy_tf_aws_ec2_base-0.5.1.tar.gz
- Upload date:
- Size: 96.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abdf3afc2f0a7e31228789ebb488e213c99c8055d61788ff17d16a4fc0fa9a4f
|
|
| MD5 |
8038543a09025fcac9c24c20c9feeee7
|
|
| BLAKE2b-256 |
2df49c485d55af5ddce219234e45612b3699477f70f6422c989e1d562bcaafb6
|
File details
Details for the file jupyter_deploy_tf_aws_ec2_base-0.5.1-py3-none-any.whl.
File metadata
- Download URL: jupyter_deploy_tf_aws_ec2_base-0.5.1-py3-none-any.whl
- Upload date:
- Size: 86.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8806144b4e3288b654194ddfcf537ebbcf40fb4f2001a733c6948b6dd6886471
|
|
| MD5 |
db019fd7263a901cc42b531482f151e0
|
|
| BLAKE2b-256 |
0062f05bf3a1f55eff6175f4e024315130ed46b9463f9c4ae9b44004dfc2b8ce
|