<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Emre Baykal</title>
    <description>The latest articles on Forem by Emre Baykal (@emre_baykal_a4a7a479d48c5).</description>
    <link>https://forem.com/emre_baykal_a4a7a479d48c5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3847401%2F80842041-66f0-4ae5-bdf1-1d471dd9b006.jpg</url>
      <title>Forem: Emre Baykal</title>
      <link>https://forem.com/emre_baykal_a4a7a479d48c5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emre_baykal_a4a7a479d48c5"/>
    <language>en</language>
    <item>
      <title>Automating MySQL InnoDB Cluster Deployment for HPE Morpheus Enterprise HA</title>
      <dc:creator>Emre Baykal</dc:creator>
      <pubDate>Sun, 19 Apr 2026 17:02:48 +0000</pubDate>
      <link>https://forem.com/emre_baykal_a4a7a479d48c5/automating-mysql-innodb-cluster-deployment-for-hpe-morpheus-enterprise-ha-51b0</link>
      <guid>https://forem.com/emre_baykal_a4a7a479d48c5/automating-mysql-innodb-cluster-deployment-for-hpe-morpheus-enterprise-ha-51b0</guid>
      <description>&lt;h1&gt;
  
  
  Automating MySQL InnoDB Cluster Deployment for HPE Morpheus Enterprise HA Environments
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A single Python script that turns a painful, multi-hour manual process into a guided 10-minute deployment.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/emrbaykal/morpheus-innodb-cluster" rel="noopener noreferrer"&gt;emrbaykal/morpheus-innodb-cluster&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👤 &lt;strong&gt;Author:&lt;/strong&gt; Emre Baykal&lt;/p&gt;

&lt;p&gt;📜 &lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you deploy &lt;strong&gt;HPE Morpheus Enterprise&lt;/strong&gt; in a 3-Node HA configuration, the installer automatically clusters the Application Tier, OpenSearch, and RabbitMQ — but leaves the &lt;strong&gt;Transactional Database Tier (MySQL)&lt;/strong&gt; entirely in your hands. You must stand up a resilient, production-grade MySQL InnoDB Cluster &lt;em&gt;before&lt;/em&gt; you can even start the Morpheus HA installation.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/emrbaykal/morpheus-innodb-cluster" rel="noopener noreferrer"&gt;&lt;strong&gt;morpheus-innodb-cluster&lt;/strong&gt;&lt;/a&gt; project solves this with a single interactive Python script backed by modular Ansible roles — it turns a multi-day manual task into a &lt;strong&gt;10-minute guided wizard&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you've ever deployed HPE Morpheus Enterprise in a High Availability (HA) configuration, you know the drill: the application tier, messaging tier, and non-transactional database tier all install and cluster automatically across your three nodes — but the &lt;strong&gt;Transactional Database Tier&lt;/strong&gt; is left entirely in your hands. That tier is MySQL, and for a production HA environment, it needs to be a properly configured, redundant, multi-node cluster.&lt;/p&gt;

&lt;p&gt;As the &lt;a href="https://support.hpe.com/hpesc/public/docDisplay?docId=sd00007510en_us&amp;amp;page=GUID-C1061ACC-BCAF-4F7C-A413-2219EAFB7983.html" rel="noopener noreferrer"&gt;HPE Morpheus Enterprise v8.1.0 HA Installation Overview&lt;/a&gt; clearly states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"In this architecture, all tiers are deployed on three machines by HPE Morpheus Enterprise during the installation, with the exception of the Transactional Database Tier. This provides HA not just for the HPE Morpheus Enterprise Application Tier but all underlying tiers that support HPE Morpheus Enterprise. The Transactional Database Tier will remain external, either as a separate cluster or PaaS, following the supported services. An external MySQL cluster must still be set up outside of the HPE Morpheus Enterprise app nodes."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means &lt;strong&gt;you&lt;/strong&gt; are responsible for standing up a resilient, properly tuned MySQL cluster &lt;strong&gt;before&lt;/strong&gt; you can even begin the Morpheus HA installation. There is no embedded option — Morpheus disables its internal MySQL (&lt;code&gt;mysql['enable'] = false&lt;/code&gt;) and expects you to provide an external endpoint.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;morpheus-innodb-cluster&lt;/strong&gt; project comes in. It is a single interactive Python script, backed by modular Ansible roles, that automates the entire process of deploying a production-ready 3-node MySQL InnoDB Cluster on Ubuntu or RHEL-based systems.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk you through why this tool exists, what problems it solves, and how to use it from start to finish.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding HPE Morpheus Enterprise HA Architecture
&lt;/h2&gt;

&lt;p&gt;HPE Morpheus Enterprise is a unified hybrid cloud management platform that provides provisioning, orchestration, monitoring, and governance across private and public clouds. It can start as a simple single-machine instance, or it can be split into individual services per machine and configured in a high availability (HA) configuration.&lt;/p&gt;

&lt;p&gt;According to the official HPE documentation (v8.1.0, February 2026), there are &lt;strong&gt;four primary tiers&lt;/strong&gt; of services within the Morpheus appliance:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Application Tier
&lt;/h3&gt;

&lt;p&gt;The stateless services layer — Nginx and Tomcat. These can be installed across all regions and placed behind a central load balancer or geo-based load balancer. Shared storage (NFS, Amazon S3, or OpenStack Swift) is required for deployment archives, virtual image catalogs, and backups.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Transactional Database Tier (MySQL) 🎯
&lt;/h3&gt;

&lt;p&gt;This is the focus of this article. The HPE documentation states: &lt;em&gt;"The Transactional Database tier consists of a MySQL compatible database. It is recommended that a lockable clustered configuration be used, such as a Galera cluster, which can also provide high availability."&lt;/em&gt; For the recommended 3-Node HA architecture, this tier &lt;strong&gt;must be external&lt;/strong&gt; — it is not deployed by the Morpheus installer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Non-Transactional Database Tier (OpenSearch)
&lt;/h3&gt;

&lt;p&gt;Used for log aggregation, stats, metrics, and temporal data. Morpheus clusters OpenSearch &lt;strong&gt;automatically&lt;/strong&gt; during installation — no manual setup required.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Messaging Tier (RabbitMQ)
&lt;/h3&gt;

&lt;p&gt;An AMQP-based tier with STOMP protocol for agent communication. RabbitMQ needs at least 3 instances for HA. While RabbitMQ is installed automatically during Morpheus setup, it &lt;strong&gt;does require manual clustering&lt;/strong&gt; afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture Diagram
&lt;/h3&gt;

&lt;p&gt;The recommended 3-Node HA deployment looks like this: three Morpheus application nodes sit behind a load balancer, each running embedded RabbitMQ and OpenSearch. On the side, a &lt;strong&gt;separate 3-node MySQL Database Cluster&lt;/strong&gt; provides the transactional database tier, connected via port 3306. Shared storage connects to all application nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fvmum910zbt8liy5uon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fvmum910zbt8liy5uon.png" alt="HPE Morpheus 3-Node HA Architecture Diagram" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;HPE Morpheus Enterprise 3-Node HA Architecture — the external MySQL cluster on the right is exactly what this tool automates.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  MySQL Requirements for Morpheus HA
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://support.hpe.com/hpesc/public/docDisplay?docId=sd00007510en_us&amp;amp;page=GUID-2D8A0A86-2231-4239-AB44-5475B4AE0827.html" rel="noopener noreferrer"&gt;HPE 3-Node HA Install documentation&lt;/a&gt; specifies the following MySQL requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MySQL version v8.0.x&lt;/strong&gt; (minimum of v8.0.72)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL cluster with at least 3 nodes&lt;/strong&gt; for redundancy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Morpheus application nodes must have connectivity&lt;/strong&gt; to the MySQL cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also an important note: &lt;em&gt;"Morpheus does not create primary keys on all tables. If you use a clustering technology that requires primary keys, you will need to leverage the invisible primary key option in MySQL 8."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once the MySQL cluster is up, you must create the Morpheus database and user &lt;strong&gt;before&lt;/strong&gt; installing Morpheus itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create the Morpheus database&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;morpheus&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the Morpheus database user&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'morpheusDbUserPassword'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Grant required permissions&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;morpheus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;OPTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RELOAD&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in each Morpheus app node's &lt;code&gt;/etc/morpheus/morpheus.rb&lt;/code&gt;, you configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'enable'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6446&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# MySQL Router local endpoint&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_db'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_db_user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheusDbUserPassword'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;mysql['enable'] = false&lt;/code&gt; — this tells Morpheus to skip its embedded MySQL and use your external cluster instead. The host points to &lt;code&gt;127.0.0.1:6446&lt;/code&gt;, which is the &lt;strong&gt;MySQL Router&lt;/strong&gt; read-write endpoint running locally on each app node.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pain of Manual MySQL InnoDB Cluster Setup
&lt;/h2&gt;

&lt;p&gt;The HPE documentation tells you that you need an external MySQL cluster, but it doesn't deploy one for you. You're on your own. Here's what you're typically facing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Repetitive Per-Node Configuration&lt;/strong&gt; — OS tuning, kernel parameters, firewall rules for ports 3306, 33060, and 33061, NTP sync, locale settings, and MySQL installation on every node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. MySQL Installation Complexity&lt;/strong&gt; — Repository management, correct stream/version selection, package locks, and systemd overrides. On RHEL, you also deal with AppStream module conflicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. InnoDB-Specific Tuning&lt;/strong&gt; — Enable GTID mode, enforce GTID consistency, set unique &lt;code&gt;server_id&lt;/code&gt;, tune &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; to RAM, configure binary log expiration, and bind addresses correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Cluster Bootstrap Choreography&lt;/strong&gt; — &lt;code&gt;dba.configureInstance()&lt;/code&gt; on every node, &lt;code&gt;dba.createCluster()&lt;/code&gt; on the primary, &lt;code&gt;cluster.addInstance()&lt;/code&gt; for each secondary with the correct recovery method. Order matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Security Considerations&lt;/strong&gt; — MySQL root passwords, cluster admin credentials, SSH keys, and sudo escalation — all of which need to be managed securely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. OS-Specific Differences&lt;/strong&gt; — Ubuntu/Debian vs. RHEL differ in repository management, package names, services, paths, and security frameworks (AppArmor vs. SELinux).&lt;/p&gt;

&lt;p&gt;Put it all together and you're looking at multiple hours — or days — of careful, error-prone work before you can even &lt;strong&gt;begin&lt;/strong&gt; the Morpheus installation itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing morpheus-innodb-cluster
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;morpheus-innodb-cluster&lt;/strong&gt; project eliminates all of this manual toil. It is a single Python script (&lt;code&gt;innodb_cluster_setup.py&lt;/code&gt;) that orchestrates the entire deployment through an interactive 11-step wizard, backed by four modular Ansible roles that handle the actual configuration work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture at a Glance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You (on master node)
  └── innodb_cluster_setup.py  (Python orchestrator)
        ├── Step 1-9:  Interactive wizard (collect &amp;amp; validate)
        └── Step 10:   Ansible playbook execution
              ├── Role 01: OS Pre-configuration
              ├── Role 02: MySQL Installation
              ├── Role 03: InnoDB Cluster Pre-configuration
              └── Role 04: Cluster Creation (master only)
        └── Step 11:   Setup Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is designed to run &lt;strong&gt;from the master node&lt;/strong&gt; of the cluster. It installs its own dependencies (Ansible, sshpass, community.mysql collection), connects to all three nodes via SSH, and executes the Ansible playbook that does the heavy lifting. You don't need to pre-install anything other than Python 3.6+ and sudo access.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Walkthrough
&lt;/h2&gt;

&lt;p&gt;Let's walk through the entire deployment process, using screenshots from a real deployment on RHEL 9 nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Clone the repository and run the script on the node you want to be the &lt;strong&gt;primary (master)&lt;/strong&gt; node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/emrbaykal/morpheus-innodb-cluster.git
&lt;span class="nb"&gt;cd &lt;/span&gt;morpheus-innodb-cluster
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 innodb_cluster_setup.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1/11 — Environment Setup
&lt;/h3&gt;

&lt;p&gt;The script begins by detecting your operating system family and automatically installing all required prerequisites. On RHEL systems, it checks for the Ansible Automation Platform repository in subscription-manager; if it's not enabled, the script gracefully falls back to installing Ansible via pip3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv27r168zuexg8jy6fe2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv27r168zuexg8jy6fe2.png" alt="Step 1 - Environment Setup" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above, you can see the tool detecting RHEL 9, installing &lt;code&gt;sshpass&lt;/code&gt; and &lt;code&gt;python3-pip&lt;/code&gt; via the OS package manager, falling back to pip for Ansible (since the AAP repository wasn't enabled in subscription-manager), and installing the &lt;code&gt;community.mysql&lt;/code&gt; Ansible collection. The entire environment is ready in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2/11 — Cluster Configuration
&lt;/h3&gt;

&lt;p&gt;This is the heart of the wizard. It collects all the information needed in five organized sections:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 1/5 — Cluster Nodes:&lt;/strong&gt; You provide the hostname and IP address for each of the three cluster nodes. The first node is automatically designated as the master (primary), and the other two become secondaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 2/5 — SSH Connection:&lt;/strong&gt; The script asks for the SSH user, key file (or password), privilege escalation method (sudo or dzdo), and sudo password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyyjj93ei9gbegkuxked.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyyjj93ei9gbegkuxked.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 3/5 — MySQL Credentials:&lt;/strong&gt; You set the MySQL root password and the InnoDB Cluster admin credentials. The cluster admin user (default: &lt;code&gt;clusterAdmin&lt;/code&gt;) is the account that will manage the InnoDB Cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 4/5 — Cluster Settings:&lt;/strong&gt; You name your cluster (in our case, &lt;code&gt;morpheus-cluster&lt;/code&gt;) and set a password for the MySQL Router user account (&lt;code&gt;routeruser&lt;/code&gt;) that will be created for application connectivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 5/5 — System Settings:&lt;/strong&gt; NTP server configuration for time synchronization across all nodes — &lt;strong&gt;critical&lt;/strong&gt; for Group Replication to function correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxwmnvhulibjuhgmgiau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxwmnvhulibjuhgmgiau.png" alt="Step 2 - MySQL Credentials, Cluster &amp;amp; NTP Settings" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Summary
&lt;/h3&gt;

&lt;p&gt;Before anything is written to disk or executed, the script presents a complete summary table of everything you've entered — cluster nodes with IPs, SSH connection details, MySQL configuration, and system settings. You review and confirm with &lt;code&gt;y&lt;/code&gt; before proceeding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fun140hwqj2r8n6kgzdjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fun140hwqj2r8n6kgzdjw.png" alt="Configuration Summary" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a safety net. All passwords are masked, and the configuration is saved to &lt;code&gt;cluster_config.json&lt;/code&gt; with mode &lt;code&gt;0600&lt;/code&gt; so it can be reused on subsequent runs without re-entering everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps 3–4 — Inventory Generation &amp;amp; SSH Connectivity Test
&lt;/h3&gt;

&lt;p&gt;The script generates an Ansible inventory file from your inputs and then tests SSH connectivity to every node. This catches authentication problems, firewall issues, or unreachable hosts &lt;strong&gt;before&lt;/strong&gt; the deployment begins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4hed3dyd2xpadk9rzwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4hed3dyd2xpadk9rzwr.png" alt="Steps 3-4 - Inventory Generation &amp;amp; SSH Connectivity Test" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our deployment, all three nodes — &lt;code&gt;innodb-rhel-1&lt;/code&gt; (192.168.42.100), &lt;code&gt;innodb-rhel-2&lt;/code&gt; (192.168.42.102), and &lt;code&gt;innodb-rhel-3&lt;/code&gt; (192.168.42.101) — came back as reachable. The inventory uses IP addresses throughout, making the deployment DNS-independent. Note the &lt;code&gt;ansible_ssh_common_args='-o StrictHostKeyChecking=no'&lt;/code&gt; setting, which prevents SSH from blocking first-time connections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps 5–7 — Pre-Flight Checks
&lt;/h3&gt;

&lt;p&gt;The script performs three validation checks before deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 5 — RHEL Repository Check:&lt;/strong&gt; Verifies that &lt;code&gt;rhel-9-for-x86_64-baseos-rpms&lt;/code&gt; and &lt;code&gt;rhel-9-for-x86_64-appstream-rpms&lt;/code&gt; are enabled on all nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 6 — Pre-Flight MySQL Package Check:&lt;/strong&gt; Scans all nodes for pre-existing &lt;code&gt;mysql-server&lt;/code&gt; and &lt;code&gt;mysql-shell&lt;/code&gt; installations that could cause conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 7 — Internet Connectivity Check:&lt;/strong&gt; Tests that all nodes can reach &lt;code&gt;dev.mysql.com&lt;/code&gt; to download MySQL packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjm7e1sw0ob8a6i1o722s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjm7e1sw0ob8a6i1o722s.png" alt="Steps 5-7 - Pre-Flight Checks" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8/11 — MySQL Version Selection
&lt;/h3&gt;

&lt;p&gt;On RHEL systems, the script presents the available MySQL AppStream streams (8.0 and 8.4) and then lists the specific versions within your chosen stream. In our deployment, we selected &lt;strong&gt;MySQL 8.4.7&lt;/strong&gt; from the 8.4 LTS stream — well above the HPE minimum requirement of v8.0.72.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg3wjgv1bp2df5rhx7w4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg3wjgv1bp2df5rhx7w4.png" alt="Step 8 - MySQL Version Selection" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9/11 — Deployment Confirmation
&lt;/h3&gt;

&lt;p&gt;A final confirmation screen shows exactly what will be deployed: the cluster name (&lt;code&gt;morpheus-cluster&lt;/code&gt;), admin user (&lt;code&gt;clusterAdmin&lt;/code&gt;), and all three nodes with their roles. The master node is marked with a star.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupa2c6s2mzvok82rtijj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupa2c6s2mzvok82rtijj.png" alt="Step 9 - Deployment Confirmation" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10/11 — Ansible Playbook Execution
&lt;/h3&gt;

&lt;p&gt;Once you confirm, the Ansible playbook executes with real-time streaming output. You can watch every task as it runs across all three nodes. The playbook applies five roles in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;pre_tasks&lt;/strong&gt; — Populates &lt;code&gt;/etc/hosts&lt;/code&gt; with cluster node entries and sets hostnames on all nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;01-os-preconfigure&lt;/strong&gt; — Deploys SSH banners, disables SELinux/AppArmor, configures firewall rules (ports 3306, 33060, 33061), sets locale, installs and configures NTP (chrony/systemd-timesyncd), tunes kernel parameters for database workloads, disables Transparent Huge Pages, and sets MySQL-specific system limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02-mysql-install&lt;/strong&gt; — Adds the MySQL repository, selects the AppStream stream/version, installs MySQL Server and MySQL Shell, configures systemd overrides with NUMA interleaving, and sets the root password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03-mysql-innodb-cluster&lt;/strong&gt; — Creates the cluster admin user, removes anonymous users, drops the test database, writes InnoDB-optimized &lt;code&gt;my.cnf&lt;/code&gt; configuration (with buffer pool auto-sized to 80% of RAM), enables GTID mode, and restarts MySQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;04-mysql-create-innodb-cluster&lt;/strong&gt; — Runs on the master node only: configures instances via &lt;code&gt;dba.configureInstance()&lt;/code&gt; in MySQL Shell, creates the cluster with &lt;code&gt;dba.createCluster()&lt;/code&gt;, adds secondary nodes with clone-based recovery, verifies cluster status, and creates the &lt;code&gt;routeruser&lt;/code&gt; account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr223n9mviq6hn9zbxzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr223n9mviq6hn9zbxzf.png" alt="Step 10 - Ansible Playbook Execution" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our deployment, the entire Ansible playbook completed in &lt;strong&gt;4 minutes and 31 seconds&lt;/strong&gt; with zero failures across all three nodes. ⚡&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 11/11 — Setup Report
&lt;/h3&gt;

&lt;p&gt;After completion, the script generates a comprehensive setup report that includes everything you need to verify and manage your new cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0i8u1pseo7er817ue3qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0i8u1pseo7er817ue3qn.png" alt="Step 11 - Setup Report Header" width="800" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;NODE STATUS&lt;/strong&gt; section shows the Ansible PLAY RECAP for each node — in our deployment, the master node had 70 OK / 32 Changed tasks, while each secondary had 61 OK / 26 Changed, with zero unreachable or failed across the board.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;APPLIED ROLES&lt;/strong&gt; section shows what each role did, and the &lt;strong&gt;NEXT STEPS&lt;/strong&gt; section provides ready-to-use commands:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggzky2d0gigvrgpnsaxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggzky2d0gigvrgpnsaxp.png" alt="Step 11 - Node Status &amp;amp; Next Steps" width="800" height="683"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick cluster status check&lt;/span&gt;
mysqlsh clusterAdmin@192.168.42.100 &lt;span class="nt"&gt;--&lt;/span&gt; cluster status

&lt;span class="c"&gt;# Interactive cluster management&lt;/span&gt;
mysqlsh clusterAdmin@192.168.42.100
var cluster &lt;span class="o"&gt;=&lt;/span&gt; dba.getCluster&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
cluster.status&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Bootstrap MySQL Router (run on each Morpheus app node)&lt;/span&gt;
mysqlrouter &lt;span class="nt"&gt;--bootstrap&lt;/span&gt; routeruser@192.168.42.100:3306 &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysqlrouter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Makes This Project Different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Truly Idempotent
&lt;/h3&gt;

&lt;p&gt;The entire workflow is safe to re-run. The script saves your configuration to &lt;code&gt;cluster_config.json&lt;/code&gt; and offers to reuse it on subsequent runs. Ansible tasks check the current state before making changes. MySQL root password handling tries socket authentication first, then falls back to the existing password.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ OS-Aware Automation
&lt;/h3&gt;

&lt;p&gt;The Ansible roles automatically detect &lt;code&gt;ansible_os_family&lt;/code&gt; and execute the appropriate tasks for each distribution. Whether you're running Ubuntu 22.04, Ubuntu 24.04, RHEL 8, or RHEL 9, the same script handles everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Production-Grade Tuning
&lt;/h3&gt;

&lt;p&gt;This isn't just a "get MySQL running" script. It applies production-grade OS and database tuning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kernel parameters:&lt;/strong&gt; TCP backlog, connection queue, TIME_WAIT reuse, swap minimization, dirty page ratios, file descriptor limits, and async I/O limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL limits:&lt;/strong&gt; 65,535 open files, 65,535 processes, unlimited memory lock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NUMA interleaving:&lt;/strong&gt; MySQL runs with &lt;code&gt;numactl --interleave=all&lt;/code&gt; for optimal memory access on multi-socket servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;InnoDB buffer pool:&lt;/strong&gt; Automatically sized to 80% of total RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTID-based replication:&lt;/strong&gt; Required for InnoDB Cluster and enabled automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary log management:&lt;/strong&gt; Automatic purge after 7 days&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Security by Default
&lt;/h3&gt;

&lt;p&gt;All generated configuration files are created with mode &lt;code&gt;0600&lt;/code&gt;. Passwords are never logged (Ansible &lt;code&gt;no_log: true&lt;/code&gt;). Terminal input is masked for all password prompts. Temporary credential files in &lt;code&gt;/tmp/&lt;/code&gt; are cleaned up after cluster creation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting Morpheus to Your New Cluster
&lt;/h2&gt;

&lt;p&gt;Once the InnoDB Cluster is running, you need to bridge it with your Morpheus application nodes. Here's the complete workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Morpheus Database
&lt;/h3&gt;

&lt;p&gt;Log into your new cluster's primary node and create the database and user that Morpheus expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;morpheus&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'morpheusDbUserPassword'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;morpheus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;OPTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RELOAD&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Bootstrap MySQL Router on Each App Node
&lt;/h3&gt;

&lt;p&gt;Install MySQL Router on each Morpheus application node and bootstrap it against the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqlrouter &lt;span class="nt"&gt;--bootstrap&lt;/span&gt; routeruser@192.168.42.100:3306 &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysqlrouter
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;mysqlrouter &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl start mysqlrouter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MySQL Router will automatically discover all cluster members and create local read-write (port 6446) and read-only (port 6447) endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configure Morpheus
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/morpheus/morpheus.rb&lt;/code&gt; on each app node, pointing MySQL to the local router endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'enable'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6446&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_db'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_db_user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheus'&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'morpheus_password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'morpheusDbUserPassword'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reconfigure and proceed with the rest of the Morpheus HA installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;morpheus-ctl reconfigure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, &lt;strong&gt;MySQL Router handles automatic failover&lt;/strong&gt;. If the primary node goes down, Group Replication elects a new primary, and MySQL Router transparently redirects traffic — all without any Morpheus downtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Network Requirements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MySQL InnoDB Cluster Ports (between DB nodes)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3306&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;MySQL Classic Protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33060&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;MySQL X Protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33061&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;Group Replication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Morpheus App Node to MySQL Cluster
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3306&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;MySQL connection (or via Router 6446/6447)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Additional Morpheus HA Ports (for reference)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;HTTPS (inbound from users/agents)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4369&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;RabbitMQ EPMD (inter-node discovery)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5671/5672&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;RabbitMQ (TLS/non-TLS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9200&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;OpenSearch API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9300&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;OpenSearch inter-node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25672&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;RabbitMQ inter-node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61613/61614&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;STOMP (non-TLS/TLS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  PaaS Alternatives
&lt;/h2&gt;

&lt;p&gt;The HPE documentation also lists supported PaaS offerings as alternatives to self-managed MySQL clusters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cloud&lt;/th&gt;
&lt;th&gt;Database (MySQL)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;Amazon Aurora&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCP&lt;/td&gt;
&lt;td&gt;MySQL Instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCI&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alibaba&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, PaaS support for MySQL is limited to AWS and GCP. For &lt;strong&gt;on-premises deployments, private cloud, or any other environment&lt;/strong&gt;, a self-managed MySQL cluster is your only option — which is exactly what this tool provides.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Setting up a MySQL InnoDB Cluster for HPE Morpheus Enterprise HA shouldn't be a multi-day project requiring deep MySQL expertise. The &lt;strong&gt;morpheus-innodb-cluster&lt;/strong&gt; tool reduces it to a single command and a 10-minute guided wizard. It handles OS detection, prerequisite installation, interactive configuration, pre-flight validation, Ansible-driven deployment, and post-deployment reporting — all in one cohesive workflow.&lt;/p&gt;

&lt;p&gt;While HPE Morpheus Enterprise excels at automatically clustering OpenSearch and providing the framework for RabbitMQ clustering, the Transactional Database Tier remains the one piece that administrators must provision themselves. This tool fills that gap, giving you a consistent, repeatable, production-ready MySQL InnoDB Cluster every time.&lt;/p&gt;

&lt;p&gt;Whether you're deploying Morpheus HA for the first time or rebuilding your database tier after a migration, you're three commands away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/emrbaykal/morpheus-innodb-cluster.git
&lt;span class="nb"&gt;cd &lt;/span&gt;morpheus-innodb-cluster
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 innodb_cluster_setup.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;If you found this useful, give the &lt;a href="https://github.com/emrbaykal/morpheus-innodb-cluster" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; a ⭐ and feel free to open issues or contribute.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.hpe.com/hpesc/public/docDisplay?docId=sd00007510en_us&amp;amp;page=GUID-C1061ACC-BCAF-4F7C-A413-2219EAFB7983.html" rel="noopener noreferrer"&gt;HPE Morpheus Enterprise v8.1.0 — HA Installation Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.hpe.com/hpesc/public/docDisplay?docId=sd00007510en_us&amp;amp;page=GUID-2D8A0A86-2231-4239-AB44-5475B4AE0827.html" rel="noopener noreferrer"&gt;HPE Morpheus Enterprise v8.1.0 — 3-Node HA Install Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/mysql-innodb-cluster-introduction.html" rel="noopener noreferrer"&gt;MySQL InnoDB Cluster Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.mysql.com/doc/mysql-shell/8.4/en/deploying-production-innodb-cluster.html" rel="noopener noreferrer"&gt;MySQL Shell — Deploying Production InnoDB Cluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mysql</category>
      <category>ansible</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>HPE Morpheus Enterprise &amp; VM Essentials SAML Integration with Keycloak: A Complete Technical Guide</title>
      <dc:creator>Emre Baykal</dc:creator>
      <pubDate>Sat, 28 Mar 2026 17:26:07 +0000</pubDate>
      <link>https://forem.com/emre_baykal_a4a7a479d48c5/hpe-morpheus-enterprise-vm-essentials-saml-integration-with-keycloak-a-complete-technical-guide-7a8</link>
      <guid>https://forem.com/emre_baykal_a4a7a479d48c5/hpe-morpheus-enterprise-vm-essentials-saml-integration-with-keycloak-a-complete-technical-guide-7a8</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 What is SAML 2.0?
&lt;/h3&gt;

&lt;p&gt;SAML (Security Assertion Markup Language) 2.0 is an XML-based open standard for exchanging authentication and authorization data between two parties: an &lt;strong&gt;Identity Provider (IdP)&lt;/strong&gt; that authenticates users, and a &lt;strong&gt;Service Provider (SP)&lt;/strong&gt; that hosts the application. Instead of every application managing its own username/password database, SAML lets you delegate authentication to a central IdP. When a user logs in once at the IdP, they get access to all connected SPs without entering credentials again — this is &lt;strong&gt;Single Sign-On (SSO)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practical terms: the user clicks "Login with SSO" on the application, gets redirected to the IdP login page, authenticates there, and is sent back to the application with a cryptographically signed XML document (the "SAML assertion") that proves who they are and what groups they belong to.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Why Keycloak?
&lt;/h3&gt;

&lt;p&gt;There are several IdP options available (Okta, Azure AD, ADFS, Ping Identity, etc.), so why Keycloak?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-source and free&lt;/strong&gt; — No per-user licensing costs, which matters at scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted&lt;/strong&gt; — Full control over your identity infrastructure; no dependency on external SaaS providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol versatility&lt;/strong&gt; — Supports SAML 2.0, OpenID Connect, and OAuth 2.0 in a single platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LDAP/AD federation&lt;/strong&gt; — Connects directly to Active Directory without migrating users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes-native&lt;/strong&gt; — Runs well as a containerized deployment with built-in clustering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNCF project&lt;/strong&gt; — Active community, regular releases, and long-term sustainability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.3 What We Will Build
&lt;/h3&gt;

&lt;p&gt;Enterprise environments demand centralized identity management. When you operate HPE Morpheus Enterprise as your cloud management platform, integrating it with a dedicated Identity Provider (IdP) via SAML 2.0 eliminates password sprawl and gives you single sign-on (SSO) across the entire infrastructure stack.&lt;/p&gt;

&lt;p&gt;In this post, we walk through the entire journey: understanding how Morpheus handles SAML under the hood, deploying Keycloak on Kubernetes as your IdP, wiring the two together, and diagnosing issues when things do not go as planned. Every configuration value and YAML snippet comes from a real lab deployment, so you can replicate it in your own environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lab environment:&lt;/strong&gt; Kubernetes (single-node / Minikube compatible), Keycloak 26.x, Morpheus Enterprise 8.x, Active Directory for user federation.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Morpheus SAML Architecture
&lt;/h2&gt;

&lt;p&gt;HPE Morpheus Enterprise supports SAML 2.0 as an Identity Source type within its Administration panel. Understanding how Morpheus participates in the SAML exchange is essential before configuring anything on the Keycloak side.&lt;/p&gt;

&lt;p&gt;📸 IMAGE: Morpheus &amp;gt; Login Screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpkhxjfj01eoynggqsyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpkhxjfj01eoynggqsyg.png" alt=" " width="695" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📸 IMAGE: Morpheus &amp;gt; Forward Keycloak Login Screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8v0yna8llvpcbe0imcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8v0yna8llvpcbe0imcc.png" alt=" " width="695" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Morpheus as a SAML Service Provider (SP)
&lt;/h3&gt;

&lt;p&gt;Morpheus acts exclusively as a &lt;strong&gt;SAML Service Provider&lt;/strong&gt;. It does not function as an Identity Provider itself. When a user attempts to log in via SSO, Morpheus generates a SAML AuthnRequest, redirects the browser to the configured IdP, and then consumes the SAML Response (assertion) returned by the IdP.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Key SAML Endpoints in Morpheus
&lt;/h3&gt;

&lt;p&gt;When you create a SAML Identity Source in Morpheus, the platform automatically generates two critical values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SP Entity ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A unique identifier for Morpheus as a Service Provider. Auto-generated from the hostname, e.g. &lt;code&gt;https://morpheus-url/saml/&amp;lt;uniqueID&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SP ACS URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The callback URL where the IdP posts the SAML Response after authentication, e.g. &lt;code&gt;https://morpheus-url/externalLogin/callback/&amp;lt;uniqueID&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Login Redirect URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The IdP's SAML SSO endpoint where Morpheus sends the AuthnRequest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAML Logout Redirect URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The IdP's SAML SLO endpoint for single logout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The SP Entity ID and ACS URL are generated only after you save the Identity Source for the first time. You must save first, then copy these values to configure the IdP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.3 SAML Request and Response Configuration
&lt;/h3&gt;

&lt;p&gt;Morpheus provides granular control over how SAML requests are signed and responses are validated:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Options &amp;amp; Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAML Request&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No Signature / Self Signed / Custom RSA Signature — Controls whether AuthnRequest messages are signed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAML Response&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Do Not Validate / Validate Assertion Signature — Controls signature validation on the IdP's assertion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;POST Binding Mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ON/OFF — Uses HTTP-POST binding instead of HTTP-Redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Includes SAML Request Parameter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes/No — Whether the SAML request is included in the redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2.4 Assertion Attribute Mappings
&lt;/h3&gt;

&lt;p&gt;Morpheus maps SAML assertion attributes to internal user fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Morpheus Field&lt;/th&gt;
&lt;th&gt;Expected SAML Attribute&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Given Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;firstName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surname&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lastName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;email&lt;/code&gt; (or NameID)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2.5 Role Mapping Mechanism
&lt;/h3&gt;

&lt;p&gt;Morpheus supports role-based access control through SAML group assertions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role Mapping Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Default Role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The role assigned to all authenticated users (e.g., Standard User)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Role Attribute Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The SAML attribute containing group/role info (e.g., &lt;code&gt;groups&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Required Role Attribute Value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A group name the user must belong to for authorization (e.g., &lt;code&gt;mspusers&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;📸 IMAGE: Morpheus &amp;gt; Identity Sources &amp;gt; Keycloak&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d8izve0axdxvfqj0fu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d8izve0axdxvfqj0fu7.png" alt="Morpheus SAML SSO Configuration" width="722" height="868"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Keycloak-SAML Ecosystem: How It Works
&lt;/h2&gt;

&lt;p&gt;Keycloak is an open-source Identity and Access Management (IAM) solution maintained by the CNCF. It supports OpenID Connect, OAuth 2.0, and SAML 2.0 protocols natively. In our setup, Keycloak serves as the SAML Identity Provider (IdP) that authenticates users against an Active Directory backend via LDAP federation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Core Keycloak Concepts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Realm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A tenant-level isolation boundary. Each realm has its own users, clients, roles, and identity providers. Our realm: &lt;code&gt;morpheus-lab&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An application that delegates authentication to Keycloak. Morpheus is registered as a SAML client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Federation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Allows Keycloak to pull users from LDAP/Active Directory without duplicating credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol Mappers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transform user attributes and group memberships into SAML assertions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Roles &amp;amp; Groups&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Realm-level and client-level roles. AD groups can be synced and mapped into assertions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3.2 SAML 2.0 Authentication Flow (SP-Initiated SSO)
&lt;/h3&gt;

&lt;p&gt;The diagram below illustrates the SP-Initiated SAML SSO flow between Morpheus and Keycloak:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz38hluaexap97meqmwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz38hluaexap97meqmwe.png" alt="SAML 2.0 SP-Initiated SSO Flow Diagram" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step-by-step:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user navigates to the Morpheus login page and clicks the SSO login button.&lt;/li&gt;
&lt;li&gt;Morpheus generates a SAML AuthnRequest and redirects the user's browser to Keycloak's SAML endpoint: &lt;code&gt;https://keycloak-server:30443/realms/morpheus-lab/protocol/saml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keycloak presents the login form. The user enters their AD credentials.&lt;/li&gt;
&lt;li&gt;Keycloak authenticates the user against the federated LDAP/AD backend.&lt;/li&gt;
&lt;li&gt;On successful authentication, Keycloak constructs a &lt;strong&gt;SAML Response&lt;/strong&gt; containing signed assertions with user attributes (&lt;code&gt;firstName&lt;/code&gt;, &lt;code&gt;lastName&lt;/code&gt;) and group memberships (&lt;code&gt;groups&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Keycloak POSTs the SAML Response to the Morpheus ACS URL.&lt;/li&gt;
&lt;li&gt;Morpheus validates the assertion signature, maps attributes and roles, creates or updates the user session, and grants access.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3.3 Authentication vs Authorization
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Authentication (Who are you?)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keycloak acts as the authentication broker&lt;/strong&gt;, sitting between the application (Morpheus) and the identity store (Active Directory).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Federation (LDAP)&lt;/strong&gt; allows Keycloak to verify credentials against AD without storing passwords locally. Keycloak performs LDAP BIND operations to authenticate users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MFA&lt;/strong&gt; can be layered on top through Keycloak's authentication flow configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session Management:&lt;/strong&gt; Once authenticated, Keycloak creates a session. Subsequent SAML requests within the session's lifetime do not require re-authentication (SSO behavior).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvef5xllize3ka1tvqyt6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvef5xllize3ka1tvqyt6.png" alt="Keycloak authentication flow diagram" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Authorization (What can you do?)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keycloak syncs AD groups via the &lt;strong&gt;LDAP Group Mapper&lt;/strong&gt; (&lt;code&gt;mspusers&lt;/code&gt;, &lt;code&gt;apparchitech&lt;/code&gt;, &lt;code&gt;selfservice&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;SAML Group List Mapper&lt;/strong&gt; serializes group memberships into the SAML assertion as a &lt;code&gt;groups&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;Morpheus reads the &lt;code&gt;groups&lt;/code&gt; attribute and maps it to internal roles:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mspusers&lt;/code&gt; → authorized user (Required Role)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apparchitech&lt;/code&gt; → Application Architect role&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;selfservice&lt;/code&gt; → Self-Service User role&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Users not in the required group are &lt;strong&gt;denied access&lt;/strong&gt; even if authentication succeeds.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.4 Single Logout (SLO) Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks Logout → Morpheus sends LogoutRequest → Keycloak terminates session
→ Keycloak POSTs LogoutResponse to /login/auth → User lands on login page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; The Logout Service POST Binding URL must be set to &lt;code&gt;/login/auth&lt;/code&gt; (the login page), NOT the ACS callback URL. Morpheus's ACS handler cannot process LogoutResponse objects and throws a &lt;code&gt;GroovyCastException&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Deploying Keycloak on Kubernetes
&lt;/h2&gt;

&lt;p&gt;This section walks through deploying a production-grade Keycloak cluster on Kubernetes using a single YAML manifest.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The complete Kubernetes manifest used in this guide is available on GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/emrbaykal/morpheus-k8/blob/main/sso-k8s/keycloak/keycloak.yaml" rel="noopener noreferrer"&gt;keycloak.yaml on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4.1 Architecture Overview
&lt;/h3&gt;

&lt;p&gt;The deployment stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keycloak StatefulSet&lt;/strong&gt; (2 replicas) with Infinispan clustering for HA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL Deployment&lt;/strong&gt; with PersistentVolumeClaim (10Gi)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Secret&lt;/strong&gt; for admin and database passwords&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-signed TLS certificates&lt;/strong&gt; generated by init containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodePort Service&lt;/strong&gt; for external HTTPS access on port 30443&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless Service&lt;/strong&gt; for Infinispan/JGroups cluster discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 Prerequisites
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running Kubernetes cluster or Minikube instance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; configured and connected to your cluster&lt;/li&gt;
&lt;li&gt;At least &lt;strong&gt;4GB RAM&lt;/strong&gt; and &lt;strong&gt;2 CPU cores&lt;/strong&gt; available for the Keycloak + PostgreSQL pods&lt;/li&gt;
&lt;li&gt;A storage provisioner (default StorageClass or Rook-Ceph). For Minikube, enable it with:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube addons &lt;span class="nb"&gt;enable &lt;/span&gt;default-storageclass
minikube addons &lt;span class="nb"&gt;enable &lt;/span&gt;storage-provisioner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Minikube users:&lt;/strong&gt; Start Minikube with sufficient resources:&lt;br&gt;
&lt;code&gt;minikube start --cpus=4 --memory=8192 --driver=docker&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Network &amp;amp; DNS requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Kubernetes node IP must be reachable from the Morpheus server (for SAML redirects)&lt;/li&gt;
&lt;li&gt;The Morpheus server hostname (e.g., &lt;code&gt;morpheus-server&lt;/code&gt;) must be resolvable from both the user's browser &lt;strong&gt;and&lt;/strong&gt; the Keycloak pods. If you're using a local domain, add entries to &lt;code&gt;/etc/hosts&lt;/code&gt; on the machines or configure your internal DNS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall rules:&lt;/strong&gt; Ensure these ports are open between the components:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User Browser&lt;/td&gt;
&lt;td&gt;Morpheus Server&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Access Morpheus UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Browser&lt;/td&gt;
&lt;td&gt;K8s Node&lt;/td&gt;
&lt;td&gt;30443&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Keycloak login page (SAML redirect)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Morpheus Server&lt;/td&gt;
&lt;td&gt;K8s Node&lt;/td&gt;
&lt;td&gt;30443&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;SAML backchannel (POST binding)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;K8s Pod Network&lt;/td&gt;
&lt;td&gt;AD Domain Controller&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;td&gt;LDAP&lt;/td&gt;
&lt;td&gt;User federation / authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Active Directory requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;dedicated service account&lt;/strong&gt; for Keycloak LDAP binding (e.g., &lt;code&gt;svc-keycloak&lt;/code&gt;). This account needs &lt;strong&gt;read-only access&lt;/strong&gt; to the Users container (&lt;code&gt;CN=Users,DC=yourdomain,DC=local&lt;/code&gt;). It does not need Domain Admin privileges — basic "Read all user information" permission is sufficient&lt;/li&gt;
&lt;li&gt;AD groups that will map to Morpheus roles (e.g., &lt;code&gt;mspusers&lt;/code&gt;, &lt;code&gt;apparchitech&lt;/code&gt;, &lt;code&gt;selfservice&lt;/code&gt;) must exist and users must be members of the appropriate groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.3 Step 1: Prepare Secrets
&lt;/h3&gt;

&lt;p&gt;The YAML uses a Kubernetes Secret to store sensitive credentials. The passwords are Base64-encoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Encode your passwords&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'YourAdminPassword!'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;
&lt;span class="c"&gt;# Output: WW91*****UGFzc3***cmQh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'YourDBPassword!'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;
&lt;span class="c"&gt;# Output: WW9************mQh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Secret resource in the YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;admin-password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;base64-encoded-admin-password&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;db-password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;base64-encoded-db-password&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Never commit plain-text passwords to version control. Use a secrets manager (Vault, Sealed Secrets) in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4.4 Step 2: Namespace and Storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sso-stack&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-pvc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.5 Step 3: PostgreSQL Database
&lt;/h3&gt;

&lt;p&gt;Keycloak requires an external database in production mode. Key points from our manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:17&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
        &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak-secret&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGDATA&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data/pgdata&lt;/span&gt;
    &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-U"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keycloak"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256Mi&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512Mi&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;500m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.6 Step 4: Keycloak StatefulSet
&lt;/h3&gt;

&lt;p&gt;The Keycloak deployment uses a &lt;strong&gt;StatefulSet with 2 replicas&lt;/strong&gt; for high availability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Init Containers:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;wait-for-postgres:&lt;/strong&gt; Polls PostgreSQL port 5432 before Keycloak starts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;generate-tls-cert:&lt;/strong&gt; Generates a self-signed TLS certificate (10 years validity)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;generate-tls-cert&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.19&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;apk add --no-cache openssl&lt;/span&gt;
        &lt;span class="s"&gt;openssl req -x509 -nodes -days 3650 \&lt;/span&gt;
          &lt;span class="s"&gt;-newkey rsa:2048 \&lt;/span&gt;
          &lt;span class="s"&gt;-keyout /certs/tls.key \&lt;/span&gt;
          &lt;span class="s"&gt;-out /certs/tls.crt \&lt;/span&gt;
          &lt;span class="s"&gt;-subj "/CN=keycloak-morpheus-lab/O=Lab/C=TR"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Environment Variables:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_BOOTSTRAP_ADMIN_USERNAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initial admin username (&lt;code&gt;admin&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_BOOTSTRAP_ADMIN_PASSWORD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Admin password from Secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;KC_DB&lt;/code&gt; / &lt;code&gt;KC_DB_URL_HOST&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Database type and host (&lt;code&gt;postgres&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_HTTPS_CERTIFICATE_FILE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to TLS certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_HTTPS_CERTIFICATE_KEY_FILE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to TLS key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_CACHE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clustering mode (&lt;code&gt;ispn&lt;/code&gt; = Infinispan)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_HOSTNAME_STRICT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disabled for NodePort/self-signed setups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_FEATURES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Additional features (&lt;code&gt;token-exchange&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KC_HEALTH_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables &lt;code&gt;/health/*&lt;/code&gt; endpoints for probes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Health Probes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;startupProbe:&lt;/strong&gt; &lt;code&gt;/health/started&lt;/code&gt; — 1s interval, 600 retries (10-minute startup window)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;readinessProbe:&lt;/strong&gt; &lt;code&gt;/health/ready&lt;/code&gt; — Every 10s, 3 failures to mark unready&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;livenessProbe:&lt;/strong&gt; &lt;code&gt;/health/live&lt;/code&gt; — Every 10s, 3 failures to restart pod&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.7 Step 5: Services
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Type &amp;amp; Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;keycloak&lt;/code&gt; (ClusterIP)&lt;/td&gt;
&lt;td&gt;Internal access: ports 8080 (HTTP) and 8443 (HTTPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;keycloak-discovery&lt;/code&gt; (Headless)&lt;/td&gt;
&lt;td&gt;JGroups cluster member discovery on port 7800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;keycloak-nodeport&lt;/code&gt; (NodePort)&lt;/td&gt;
&lt;td&gt;External access: port &lt;strong&gt;30080&lt;/strong&gt; (HTTP) and &lt;strong&gt;30443&lt;/strong&gt; (HTTPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.8 Step 6: Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Apply the complete manifest&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; keycloak.yaml

&lt;span class="c"&gt;# Watch the rollout&lt;/span&gt;
kubectl rollout status statefulset/keycloak &lt;span class="nt"&gt;-n&lt;/span&gt; keycloak &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10m

&lt;span class="c"&gt;# Verify all pods are running&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; keycloak

&lt;span class="c"&gt;# Access the admin console&lt;/span&gt;
&lt;span class="c"&gt;# https://&amp;lt;node-ip&amp;gt;:30443/admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For Minikube, use &lt;code&gt;minikube ip&lt;/code&gt; to get the node IP. For a multi-node cluster, any node's IP will work with NodePort.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📸 IMAGE: Terminal output of kubectl get pods -n keycloak showing all pods running&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7ezfolhb43tk9nfz9rz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7ezfolhb43tk9nfz9rz.png" alt="Terminal output of kubectl get pods -n keycloak showing all pods running" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Keycloak-Morpheus SAML Integration Guide
&lt;/h2&gt;

&lt;p&gt;With Keycloak deployed and running, we can now configure the SAML integration. The configuration involves both the Keycloak side (IdP) and the Morpheus side (SP), and the &lt;strong&gt;order matters&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important — Configuration Order:&lt;/strong&gt;&lt;br&gt;
There is a chicken-and-egg situation here. Keycloak needs the &lt;strong&gt;SP Entity ID&lt;/strong&gt; and &lt;strong&gt;ACS URL&lt;/strong&gt; to create the SAML client, but these values are auto-generated by Morpheus only after you save an Identity Source. The correct order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Keycloak Realm and LDAP Federation first (Steps 1-2)&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;preliminary&lt;/strong&gt; Identity Source in Morpheus to obtain the SP Entity ID and ACS URL (Step 3)&lt;/li&gt;
&lt;li&gt;Use those values to create the SAML Client in Keycloak (Steps 4-7)&lt;/li&gt;
&lt;li&gt;Come back to Morpheus and complete the Identity Source configuration (Step 8)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5.1 Step 1: Create the Keycloak Realm
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Keycloak Admin Console at &lt;code&gt;https://keycloak-server:30443/admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click the realm dropdown (top-left) and select "Create Realm"&lt;/li&gt;
&lt;li&gt;Set Realm Name to: &lt;code&gt;morpheus-lab&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📸 IMAGE: Keycloak Admin Console &amp;gt; Manage Realm &amp;gt; Create Realm&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5c3rfxj98koe6lad6ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5c3rfxj98koe6lad6ng.png" alt="Keycloak Admin Console &amp;gt; Create Realm dialog" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Step 2: Configure LDAP User Federation
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;strong&gt;morpheus-lab &amp;gt; User Federation &amp;gt; Add New provider &amp;gt; LDAP&lt;/strong&gt; and configure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LAB AD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Active Directory&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ldap://active-directory:389&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;simple&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind DN&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CN=svc-keycloak,CN=Users,DC=domain,DC=domain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Users DN&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CN=Users,DC=domain,DC=domain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Username LDAP Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sAMAccountName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit Mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;READ_ONLY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Import Users&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Add Group Mapper&lt;/strong&gt; (Mappers &amp;gt; Add mapper):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mapper Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;group-ldap-mapper&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groups DN&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CN=Users,DC=domain,DC=domian&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group Name LDAP Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cn&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Membership LDAP Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;member&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;READ_ONLY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Roles Retrieve Strategy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LOAD_GROUPS_BY_MEMBER_ATTRIBUTE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; After saving, click &lt;strong&gt;'Sync all users'&lt;/strong&gt; and &lt;strong&gt;'Sync LDAP groups to Keycloak'&lt;/strong&gt; to import users and groups from Active Directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📸 IMAGE: Keycloak &amp;gt; User Federation &amp;gt; LAB AD settings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotbn2eukf4wo109g7s45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotbn2eukf4wo109g7s45.png" alt="LDAP 1" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifwk7n772eavobu2vhax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifwk7n772eavobu2vhax.png" alt="LDAP 2" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Step 3: Create Preliminary Identity Source in Morpheus (Get SP Entity ID)
&lt;/h3&gt;

&lt;p&gt;Before creating the SAML client in Keycloak, we need to obtain the SP Entity ID and ACS URL from Morpheus. These values are auto-generated and unique to your Morpheus instance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Morpheus at &lt;code&gt;https://morpheus-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Administration &amp;gt; Identity Sources&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;+ Add Identity Source&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Type&lt;/strong&gt; to &lt;code&gt;SAML SSO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Name&lt;/strong&gt; to &lt;code&gt;Keycloak-SSO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For now, enter any placeholder URL in &lt;strong&gt;Login Redirect URL&lt;/strong&gt; (e.g., &lt;code&gt;https://placeholder.local&lt;/code&gt;) — we will update this later&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After saving, Morpheus generates and displays two critical values in the Identity Source list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SP Entity ID&lt;/strong&gt; — e.g., &lt;code&gt;https://morpheus-server/saml/N3h***B***O&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SP ACS URL&lt;/strong&gt; — e.g., &lt;code&gt;https://morpheus-server/externalLogin/callback/N***3***O&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Copy both of these values!&lt;/strong&gt; You will need them in the next step to configure the SAML client in Keycloak. The &lt;code&gt;N3****BO&lt;/code&gt; part is a unique identifier generated by your Morpheus instance — yours will be different.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📸 IMAGE: Morpheus &amp;gt; Identity Sources list showing the auto-generated SP Entity ID and ACS URL&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01w6rcum5xhjfzkd2ona.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01w6rcum5xhjfzkd2ona.png" alt=" " width="731" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 Step 4: Create the SAML Client in Keycloak
&lt;/h3&gt;

&lt;p&gt;Now go back to the Keycloak Admin Console and create the SAML client using the values from the previous step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;morpheus-lab &amp;gt; Clients &amp;gt; Create Client&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set Client Type to &lt;strong&gt;SAML&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Client ID&lt;/strong&gt; to the SP Entity ID you copied from Morpheus: &lt;code&gt;https://morpheus-server/saml/N3h**K**5**&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set Name to: &lt;code&gt;Morpheus Enterprise&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Access Settings:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Root URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://morpheus-server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid Redirect URIs (ACS URL)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://morpheus-server/externalLogin/callback/N3h**K**5**&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Master SAML Processing URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://morpheus-server/externalLogin/callback/N3h**K**5**&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDP-Initiated SSO URL Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;morpheus&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;SAML Capabilities:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name ID Format&lt;/td&gt;
&lt;td&gt;&lt;code&gt;username&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Force POST Binding&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Include AuthnStatement&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Signature &amp;amp; Encryption:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sign Documents&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign Assertions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature Algorithm&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RSA_SHA256&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Signature Required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;OFF&lt;/code&gt; (CRITICAL!)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypt Assertions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OFF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; &lt;code&gt;Client Signature Required&lt;/code&gt; must be &lt;strong&gt;OFF&lt;/strong&gt;. Morpheus signs requests with a self-signed certificate that does not match the certificate registered in Keycloak. If this is ON, every SAML request from Morpheus will be rejected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📸 IMAGE: Keycloak &amp;gt; Clients &amp;gt; Morpheus Enterprise &amp;gt; Settings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0q4zq6rljlnm1qyut85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0q4zq6rljlnm1qyut85.png" alt=" " width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 Step 5: Configure Logout (Advanced Settings)
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Advanced tab &amp;gt; Fine Grain SAML Endpoint Configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logout Service POST Binding URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://morpheus-server/login/auth&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is crucial!&lt;/strong&gt; Setting this URL to &lt;code&gt;/login/auth&lt;/code&gt; prevents the &lt;code&gt;GroovyCastException&lt;/code&gt; bug. The Morpheus ACS callback handler cannot process SAML LogoutResponse objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5.6 Step 6: Configure SAML Mappers
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Client Scopes &amp;gt; dedicated &amp;gt; Mappers&lt;/strong&gt; and add:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mapper 1: Groups (Group List)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mapper Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Group list&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;groups&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Attribute NameFormat&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Basic&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single Group Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OFF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full Group Path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OFF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Mapper 2: firstName (User Attribute)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mapper Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User Attribute&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;firstName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;firstName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Mapper 3: lastName (User Attribute)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mapper Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User Attribute&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Attribute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lastName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lastName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;📸 IMAGE: Keycloak &amp;gt; Client Scopes &amp;gt; dedicated &amp;gt; Mappers list&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjq2jo6p96otyqp3i2xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjq2jo6p96otyqp3i2xb.png" alt=" " width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.7 Step 7: Copy the Realm Certificate
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;morpheus-lab &amp;gt; Realm Settings &amp;gt; Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Find the &lt;strong&gt;RS256&lt;/strong&gt; key row and click the &lt;strong&gt;Certificate&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Copy the entire certificate string&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📸 IMAGE: Keycloak &amp;gt; Realm Settings &amp;gt; Keys &amp;gt; RS256 certificate&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlg74czikm8u6dks5cpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlg74czikm8u6dks5cpv.png" alt=" " width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.8 Step 8: Complete the Morpheus Identity Source Configuration
&lt;/h3&gt;

&lt;p&gt;Now go back to the &lt;strong&gt;preliminary Identity Source&lt;/strong&gt; you created in Step 3 and update it with the real values. Navigate to &lt;strong&gt;Administration &amp;gt; Identity Sources &amp;gt; Keycloak-SSO &amp;gt; Edit&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Login Redirect URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://keycloak-server:30443/realms/morpheus-lab/protocol/saml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Logout Redirect URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://keycloak-server:30443/realms/morpheus-lab/protocol/saml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Includes SAML Request Parameter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST Binding Mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Request&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Self Signed&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Response&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Validate Assertion Signature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Response Public Key&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(paste RS256 certificate from Keycloak)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Assertion Attribute Mappings:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Morpheus Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Given Name Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;firstName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surname Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lastName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Role Mappings:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Morpheus Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default Role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Standard User&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role Attribute Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;groups&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required Role Attribute Value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mspusers&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Architect Role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apparchitech&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self Service User Role&lt;/td&gt;
&lt;td&gt;&lt;code&gt;selfservice&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;📸 IMAGE: Morpheus &amp;gt; Identity Sources &amp;gt; Keycloak-SSO configuration&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zd2fdz1gjw1za4ofo7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zd2fdz1gjw1za4ofo7t.png" alt=" " width="718" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.9 Step 9: Test the Integration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open a new browser / incognito window&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;https://morpheus-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click the SSO login option (Keycloak-SSO should appear)&lt;/li&gt;
&lt;li&gt;You will be redirected to Keycloak's login page&lt;/li&gt;
&lt;li&gt;Enter an AD username (e.g., &lt;code&gt;emre.baykal&lt;/code&gt;) and password&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. On success, you will be redirected back to Morpheus and logged in
&lt;/h2&gt;

&lt;h2&gt;
  
  
  6. Troubleshooting SAML Integration
&lt;/h2&gt;

&lt;p&gt;SAML integrations can fail silently or produce cryptic errors. This section covers the most common issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Diagnostic Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;SAML Tracer&lt;/strong&gt; (Browser Extension)&lt;/td&gt;
&lt;td&gt;Captures SAML requests/responses in real-time. Available for Firefox and Chrome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keycloak Events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enable in Realm Settings &amp;gt; Events. Shows auth attempts and errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keycloak Logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl logs statefulset/keycloak -n keycloak -f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Morpheus Logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/var/log/morpheus/morpheus-ui/current&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base64 Decoder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`echo '' \&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6.2 Common Issues and Solutions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Issue 1: 'Invalid Requester' Error
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keycloak returns 'Invalid requester' or 'Client not found'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SP Entity ID in Morpheus doesn't match Client ID in Keycloak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Copy the exact SP Entity ID from Morpheus Identity Source and use it as Client ID in Keycloak&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 2: Signature Validation Failure
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;'Signature validation failed' or 'Invalid signature'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SAML Response Public Key in Morpheus doesn't match Keycloak's RS256 certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Copy fresh certificate from Keycloak &amp;gt; Realm Settings &amp;gt; Keys &amp;gt; RS256 &amp;gt; Certificate. &lt;strong&gt;Must be updated after every Keycloak redeployment&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 3: User Authenticated but Access Denied
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User authenticates at Keycloak but gets 'Access Denied' in Morpheus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User not in the required group, or Group List mapper missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1) Verify user is in {% raw %}&lt;code&gt;mspusers&lt;/code&gt; AD group. 2) Check Group List mapper in client scopes. 3) Use SAML Tracer to verify &lt;code&gt;groups&lt;/code&gt; attribute in assertion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 4: GroovyCastException on Logout
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 error on logout with &lt;code&gt;GroovyCastException&lt;/code&gt; in logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LogoutResponse sent to ACS URL instead of login page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Set Logout Service POST Binding URL to &lt;code&gt;https://morpheus-server/login/auth&lt;/code&gt; in Keycloak Advanced settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 5: Login Loop / Redirect Loop
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser keeps redirecting between Morpheus and Keycloak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mixed HTTP/HTTPS, clock skew, or invalid ACS URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1) Ensure both use HTTPS. 2) Verify NTP sync (SAML assertions are time-sensitive). 3) Check Valid Redirect URIs matches exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 6: Attributes Not Mapped
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;firstName/lastName are empty, roles not assigned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SAML mappers missing or attribute names don't match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1) Add User Attribute mappers for &lt;code&gt;firstName&lt;/code&gt; and &lt;code&gt;lastName&lt;/code&gt;. 2) Names are &lt;strong&gt;case-sensitive&lt;/strong&gt;. 3) Use SAML Tracer to verify attributes in assertion XML&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Issue 7: LDAP Users Not Appearing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No users appear after LDAP federation setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wrong Bind DN, Users DN, or network connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1) Test connectivity from pod. 2) Verify Bind DN has read access. 3) Click 'Sync all users'. 4) Check Keycloak logs for LDAP errors&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6.3 SAML Assertion Debugging Checklist
&lt;/h3&gt;

&lt;p&gt;When debugging, use SAML Tracer to capture the assertion and verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NameID:&lt;/strong&gt; Present and in expected format (&lt;code&gt;username&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issuer:&lt;/strong&gt; Matches the Keycloak realm URL?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AudienceRestriction:&lt;/strong&gt; Audience matches Morpheus SP Entity ID?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditions/NotBefore/NotOnOrAfter:&lt;/strong&gt; Valid timestamps? Check for clock skew&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AuthnStatement:&lt;/strong&gt; Present? (Required by Morpheus)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AttributeStatement:&lt;/strong&gt; &lt;code&gt;firstName&lt;/code&gt;, &lt;code&gt;lastName&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt; attributes present with correct values?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signature:&lt;/strong&gt; Assertion signed? Certificate matches?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.4 Useful Debug Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Keycloak pod logs&lt;/span&gt;
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; statefulset/keycloak &lt;span class="nt"&gt;-n&lt;/span&gt; keycloak

&lt;span class="c"&gt;# Decode a SAML Response from browser&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;base64-saml-response&amp;gt;'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | xmllint &lt;span class="nt"&gt;--format&lt;/span&gt; -

&lt;span class="c"&gt;# Test LDAP connectivity from inside the cluster&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; keycloak-0 &lt;span class="nt"&gt;-n&lt;/span&gt; keycloak &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'nc -zv domain-controller 389'&lt;/span&gt;

&lt;span class="c"&gt;# Check Keycloak health&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; keycloak-0 &lt;span class="nt"&gt;-n&lt;/span&gt; keycloak &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="nt"&gt;-sk&lt;/span&gt; https://localhost:8443/health/ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating Morpheus Enterprise with Keycloak via SAML provides a robust, centralized authentication solution for enterprise cloud management. Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Morpheus acts as a SAML SP;&lt;/strong&gt; Keycloak acts as the SAML IdP with AD backend&lt;/li&gt;
&lt;li&gt;The Kubernetes deployment uses a &lt;strong&gt;StatefulSet with Infinispan clustering&lt;/strong&gt; for HA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-signed TLS certificates&lt;/strong&gt; are generated by init containers — no external cert-manager required for lab environments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Client Signature Required&lt;/code&gt; must be &lt;strong&gt;OFF&lt;/strong&gt; in Keycloak for Morpheus compatibility&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Logout Service POST Binding URL&lt;/strong&gt; must point to &lt;code&gt;/login/auth&lt;/code&gt; to avoid the &lt;code&gt;GroovyCastException&lt;/code&gt; bug&lt;/li&gt;
&lt;li&gt;Always &lt;strong&gt;update the SAML Response Public Key&lt;/strong&gt; in Morpheus after redeploying Keycloak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions or ran into a different issue? Drop a comment below!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by Emre Baykal — March 2026&lt;/em&gt;&lt;/p&gt;

</description>
      <category>keycloak</category>
      <category>sso</category>
      <category>kubernetes</category>
      <category>morpheus</category>
    </item>
  </channel>
</rss>
