diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 18e0edb5b66b9ce028b9387915ed9a55436d7ffd..08675a26ee4e332f489bb2869194aaea102aec5c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,6 +136,43 @@ build_ssh_proxy_image:
     - if: $PIPELINE_TARGET == "build" && $BUILD_TARGET == "ssh-proxy"
       when: always
 
+.build_login_image_template: &build_login_image_template
+  script:
+    - *update_ansible_repo
+    - *get_ansible_files
+    # packer vars for job env
+    - export PKR_VAR_flavor="${PROXY_BUILD_FLAVOR:-$PKR_VAR_flavor}"
+    - export PKR_VAR_build_instance_name="${BUILD_TARGET}-${EXT_REPO_HEAD}"
+    - export PKR_VAR_image_date_suffix=false
+    - |
+      if [ $CI_PIPELINE_SOURCE == 'merge_request_event' ]; then
+        export PKR_VAR_image_name="${BUILD_TARGET}-PR-${CI_MERGE_REQUEST_IID}"
+      elif [ $CI_PIPELINE_SOURCE == 'schedule' ]; then
+        export PKR_VAR_image_name="${BUILD_TARGET}-${BUILD_DATE}"
+      fi
+    # packer commands
+    - packer init openstack-login
+    - packer validate openstack-login
+    - packer build -machine-readable openstack-login | tee login_build.log
+    - export BUILT_LOGIN_IMAGE_ID=$(grep 'Image:' login_build.log | awk '{print $4}')
+    - echo BUILT_LOGIN_IMAGE_ID=${BUILT_LOGIN_IMAGE_ID} | tee -a $CI_PROJECT_DIR/image.env
+    # set image properties with repo state
+    - openstack image set --property EXT_PR_SRC_REPO=${EXT_PR_SRC_REPO} --property EXT_PR_SRC_BRANCH_SHA=${EXT_PR_SRC_BRANCH_SHA} --property EXT_PR_TARGET_REPO=${EXT_PR_TARGET_REPO} --property EXT_PR_TARGET_BRANCH_SHA=${EXT_PR_TARGET_BRANCH_SHA} --property PACKER_IMAGE_HEAD=${CI_COMMIT_SHORT_SHA} ${BUILT_LOGIN_IMAGE_ID}
+  artifacts:
+    reports:
+      dotenv: image.env
+
+build_login_image:
+  stage: build
+  environment:
+    name: $ENV
+  tags:
+    - build
+  <<: *build_login_image_template
+  rules:
+    - if: $PIPELINE_TARGET == "build" && $BUILD_TARGET == "login"
+      when: always
+
 deploy_http_proxy_node:
   stage: deploy
   environment:
@@ -236,3 +273,54 @@ deploy_ssh_proxy_node:
   rules:
     - if: $PIPELINE_TARGET == "deploy" && $SSH_PROXY_IMAGE_ID
       when: always
+
+deploy_login_node:
+  stage: deploy
+  environment:
+    name: $ENV
+  tags:
+    - build
+  script:
+    - openstack image set --accept $LOGIN_IMAGE_ID || true
+    - FAILED=false
+    - |
+      cat > user_data.txt <<EOF
+      #!/bin/bash
+      cat >> /etc/NetworkManager/conf.d/90-dns-none.conf<<EEOF
+      [main]
+      dns=none
+      EEOF
+      systemctl reload NetworkManager
+      echo "$DEV_KEY" >> /root/.ssh/authorized_keys
+      ip route replace default via ${DEFAULT_GATEWAY_IP} dev eth0
+      git clone ${CI_REPOSITORY_URL} /tmp/${CI_PROJECT_NAME}
+      cd /tmp/${CI_PROJECT_NAME}
+      git checkout ${CI_COMMIT_REF_NAME}
+      cat >> ansible/hosts<<EEOF
+      [$ENV]
+      127.0.0.1
+      EEOF
+      ansible-playbook -c local -i ansible/hosts --extra-vars="$EXTRA_VARS" ansible/cluster.yml | tee -a /tmp/ansible.log
+      rm -rf /tmp/${CI_PROJECT_NAME}
+      EOF
+    - |
+      export cmd="openstack server create"
+      cmd+=" -c id -f value --image $LOGIN_IMAGE_ID"
+      cmd+=" --flavor $INSTANCE_FLAVOR"
+      cmd+=" --network $INSTANCE_NETWORK"
+      cmd+=" --security-group allow-ssh"
+      cmd+=" --user-data user_data.txt"
+      if [ -n "$LOGIN_PORT" ];then cmd+=" --port $LOGIN_PORT"; fi
+      cmd+=" --wait $LOGIN_INSTANCE_NAME"
+    - export LOGIN_INSTANCE_ID=$(bash -c "$cmd")
+    - |
+      # Associate the floating IP(s) with the SSH Proxy instance
+      for LOGIN_FLOATING_IP in ${LOGIN_FLOATING_IP_LIST[@]};
+      do
+        echo "Associating FLOATING_IP $LOGIN_FLOATING_IP with LOGIN_INSTANCE_ID $LOGIN_INSTANCE_ID"
+        openstack server add floating ip $LOGIN_INSTANCE_ID $LOGIN_FLOATING_IP
+      done
+  rules:
+    - if: $PIPELINE_TARGET == "deploy" && $LOGIN_IMAGE_ID
+      when: always
+
diff --git a/ansible/compute.yml b/ansible/compute.yml
index 2907d08077a8431a717209aec22fc362bf2391a7..400992914c594449e23f024038a4b5c37076f95b 100644
--- a/ansible/compute.yml
+++ b/ansible/compute.yml
@@ -8,5 +8,3 @@
     - { name: 'pam_slurm_adopt', tags: 'pam_slurm_adopt' }
     - { name: 'install_nhc', tags: 'install_nhc'}
 
-- name: Setup node for use as a virtual cheaha node
-  ansible.builtin.import_playbook: cheaha.yml
diff --git a/openstack-compute/README.md b/openstack-login/README.md
similarity index 100%
rename from openstack-compute/README.md
rename to openstack-login/README.md
diff --git a/openstack-compute/nodeimage.pkr.hcl b/openstack-login/nodeimage.pkr.hcl
similarity index 85%
rename from openstack-compute/nodeimage.pkr.hcl
rename to openstack-login/nodeimage.pkr.hcl
index 15941bab1900a056d3d67bdba2db2bd5d94a31fb..008b7683c26e97119664bfbb03c19ea7d8ee8fcf 100644
--- a/openstack-compute/nodeimage.pkr.hcl
+++ b/openstack-login/nodeimage.pkr.hcl
@@ -36,6 +36,14 @@ source "openstack" "image" {
 build {
   sources = ["source.openstack.image"]
 
+  provisioner "shell" {
+    inline = [
+      "sudo yum install -y libselinux-python3 python3 python3-pip tmux vim git bash-completion curl wget unzip",
+      "sudo python3 -m pip install --upgrade pip",
+      "sudo pip3 install s3cmd==2.3.0 ansible==4.10.0 python-openstackclient==5.8.0"
+    ]
+  }
+
   provisioner "ansible" {
     use_proxy     = false
     user          = var.ssh_username
diff --git a/openstack-compute/variables.pkr.hcl b/openstack-login/variables.pkr.hcl
similarity index 99%
rename from openstack-compute/variables.pkr.hcl
rename to openstack-login/variables.pkr.hcl
index 20efd641406f81250b3cb1cc6514f5078ee2a503..d3681394fe7c66990a5b9f1507f38f7dbc74d025 100644
--- a/openstack-compute/variables.pkr.hcl
+++ b/openstack-login/variables.pkr.hcl
@@ -1,6 +1,7 @@
 variable "root_ssh_key" {
   type        = string
   description = "The root key to use for ssh"
+  default     = ""
 }
 
 variable "image_name" {