> For the complete documentation index, see [llms.txt](https://docs.neevcloud.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neevcloud.com/neevcloud-guide/stack-automation-library/development-and-tools/jupyterlab-workspace.md).

# JupyterLab Workspace

Deploys a Instance with Python and JupyterLab pre-installed. Accessible through a browser. Useful for data science, ML experimentation, or quick Python development.

#### Architecture:

* 1 Instance (StellarSurge — 4 vCPU, 16 GB RAM)
* 1 private network (10.200.0.0/24)
* 1 floating IP (public access)
* 60 GB boot volume
* Security group allows SSH, port 8888

#### What is automated:

* Instance creation with selected flavor and image
* Private network, subnet, and router
* Floating IP assigned to Instance
* Security group with ports 22, 8888
* Python 3 venv created under dedicated `jupyter` user
* Installed: JupyterLab, NumPy, Pandas, Scikit-learn, Matplotlib, Seaborn, Transformers, PyTorch (CPU)
* JupyterLab configured with random password
* Runs as systemd service (auto-restart on reboot)
* Credentials saved to `/root/.jupyter_credentials`

#### Before you launch:

Update these parameters in the template or at launch time:

| Parameter        | Default                | What to change                                                                 |
| ---------------- | ---------------------- | ------------------------------------------------------------------------------ |
| `key_name`       | `stack-test`           | **Required.** Replace with your SSH keypair name from **Compute - Key Pairs**. |
| `image`          | `Ubuntu 22.04 Updated` | Change only if you need a different OS image.                                  |
| `flavor`         | `StellarSurge`         | **Minimum StellarSurge (4 vCPU, 16 GB).** Do not use smaller flavors.          |
| `volume_size`    | `60`                   | Increase if you plan to work with large datasets (in GB).                      |
| `public_network` | `Public`               | Do not change unless your cloud has a different external network name.         |

{% hint style="info" %}
**key\_name is the only parameter you must change before launching.** Everything else works with defaults.
{% endhint %}

#### JupyterLab Template

Save as `jupyterlab.yaml` and upload via Orchestration or Past Direct.

```
heat_template_version: 2021-04-16
description: >
  NeevCloud: JupyterLab AI Workspace with Python data science stack.
  Flavor minimum: StellarSurge (4 vCPU, 16 GB RAM).
  JupyterLab accessible on port 8888.

parameters:
  image:
    type: string
    default: Ubuntu 22.04 Updated
  flavor:
    type: string
    default: StellarSurge
  volume_size:
    type: number
    description: Boot volume size in GB
    default: 60
  public_network:
    type: string
    default: Public
  key_name:
    type: string
    default: stack-test

resources:
  net:
    type: OS::Neutron::Net
    properties:
      name: jupyter-net

  subnet:
    type: OS::Neutron::Subnet
    properties:
      network: { get_resource: net }
      cidr: 10.200.0.0/24
      dns_nameservers: [8.8.8.8, 8.8.4.4]

  router:
    type: OS::Neutron::Router
    properties:
      external_gateway_info:
        network: { get_param: public_network }

  router_interface:
    type: OS::Neutron::RouterInterface
    properties:
      router: { get_resource: router }
      subnet: { get_resource: subnet }

  sg:
    type: OS::Neutron::SecurityGroup
    properties:
      name: jupyter-sg
      rules:
        - { direction: ingress, protocol: tcp, port_range_min: 22, port_range_max: 22, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: tcp, port_range_min: 8888, port_range_max: 8888, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: icmp, remote_ip_prefix: 0.0.0.0/0 }

  server:
    type: OS::Nova::Server
    properties:
      name: jupyterlab-server
      flavor: { get_param: flavor }
      key_name: { get_param: key_name }
      networks:
        - network: { get_resource: net }
      security_groups:
        - { get_resource: sg }
      block_device_mapping_v2:
        - boot_index: 0
          delete_on_termination: true
          image: { get_param: image }
          volume_size: { get_param: volume_size }
      user_data_format: RAW
      user_data: |
        #!/bin/bash
        set -e
        export DEBIAN_FRONTEND=noninteractive

        JUPYTER_PASS=$(openssl rand -hex 12)

        apt-get update -y
        apt-get install -y python3 python3-pip python3-venv

        # Create dedicated user and venv
        useradd -m -s /bin/bash jupyter
        su - jupyter -c "python3 -m venv /home/jupyter/venv"
        su - jupyter -c "/home/jupyter/venv/bin/pip install --upgrade pip"
        su - jupyter -c "/home/jupyter/venv/bin/pip install jupyterlab numpy pandas scikit-learn matplotlib seaborn transformers torch --extra-index-url https://download.pytorch.org/whl/cpu"

        # Generate hashed password
        HASH=$(su - jupyter -c "/home/jupyter/venv/bin/python3 -c \"from jupyter_server.auth import passwd; print(passwd('${JUPYTER_PASS}'))\"")

        # JupyterLab config
        mkdir -p /home/jupyter/.jupyter
        cat > /home/jupyter/.jupyter/jupyter_lab_config.py <<JCFG
        c.ServerApp.ip = '0.0.0.0'
        c.ServerApp.port = 8888
        c.ServerApp.open_browser = False
        c.ServerApp.password = '${HASH}'
        c.ServerApp.allow_remote_access = True
        JCFG
        chown -R jupyter:jupyter /home/jupyter/.jupyter

        # Systemd service
        cat > /etc/systemd/system/jupyterlab.service <<SVC
        [Unit]
        Description=JupyterLab
        After=network.target

        [Service]
        User=jupyter
        WorkingDirectory=/home/jupyter
        ExecStart=/home/jupyter/venv/bin/jupyter lab --config=/home/jupyter/.jupyter/jupyter_lab_config.py
        Restart=always

        [Install]
        WantedBy=multi-user.target
        SVC

        systemctl daemon-reload
        systemctl enable jupyterlab && systemctl start jupyterlab

        cat > /root/.jupyter_credentials <<EOF
        JupyterLab Password: ${JUPYTER_PASS}
        URL: http://<FLOATING_IP>:8888
        EOF
        chmod 600 /root/.jupyter_credentials

  fip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: { get_param: public_network }

  fip_assoc:
    type: OS::Neutron::FloatingIPAssociation
    properties:
      floatingip_id: { get_resource: fip }
      port_id: { get_attr: [server, addresses, { get_resource: net }, 0, port] }

outputs:
  jupyterlab_url:
    description: JupyterLab URL
    value:
      str_replace:
        template: http://HOST:8888
        params:
          HOST: { get_attr: [fip, floating_ip_address] }
  floating_ip:
    description: Public IP
    value: { get_attr: [fip, floating_ip_address] }
  credentials_note:
    description: JupyterLab password location
    value: "SSH into server and run: cat /root/.jupyter_credentials"
```

#### Access:

* JupyterLab: <http://floating-ip:8888>
* SSH: ssh ubuntu\@floating-ip
* Password: `sudo cat /root/.jupyter_credentials`

#### Deploy:

1. Go to [Orchestration](/neevcloud-products/orchestration-infrastructure-as-code/web-server-stack-template.md)
2. Upload jupyterlab.yaml
3. Select keypair, flavor, image
4. Launch stack
5. Open `http://floating-ip:8888` and enter the password from credentials file

{% hint style="info" %}
**Note:** First boot takes 5–8 minutes for pip packages to install. If port 8888 is not responding, wait and retry.
{% endhint %}

***
