{"id":79854,"date":"2026-11-12T10:01:32","date_gmt":"2026-11-12T10:01:32","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=79854"},"modified":"2026-11-12T10:01:36","modified_gmt":"2026-11-12T10:01:36","slug":"deploying-nexus-certificate-manager-cm-and-protocol-gateway-pgwy-on-kubernetes","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/deploying-nexus-certificate-manager-cm-and-protocol-gateway-pgwy-on-kubernetes\/","title":{"rendered":"Deploying Nexus Certificate Manager (CM) and Protocol Gateway (PGWY) on Kubernetes"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Step-by-step tutorial, written from an actual deployment of&nbsp;<strong>Certificate Manager 8.15.1-1<\/strong>&nbsp;(CF server image&nbsp;<code>8.15.1-1<\/code>, PGW image&nbsp;<code>8.15.1-1<\/code>) on a 2-node kubeadm cluster.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Status of this document (2026-11-12)<\/strong>&nbsp;Steps 1-8 were executed and verified.&nbsp;<strong>Step 9 (license) is pending<\/strong>: CF will not start without a Nexus-issued&nbsp;<code>*.license<\/code>&nbsp;file, and none was available. Step 9 now contains the full procedure to obtain and apply it. Steps 10-12 (verification, bootstrap, protocol enablement) cannot be completed until then and are written from the chart contents and vendor guidance, not from a tested run. They are marked&nbsp;<strong>(untested)<\/strong>. See the&nbsp;<em>Completion checklist<\/em>&nbsp;near the end for what is done and what remains.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">0. Environment used<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Item<\/th><th class=\"has-text-align-left\" data-align=\"left\">Value<\/th><\/tr><\/thead><tbody><tr><td>Kubernetes<\/td><td>v1.35.10 (kubeadm), containerd 2.2.1, Calico v3.31<\/td><\/tr><tr><td>Nodes<\/td><td>1 control-plane (<code>16.4.37.44<\/code>), 1 worker (<code>13.204.63.105<\/code>), Ubuntu 24.04, 2 vCPU \/ 8 GB each<\/td><\/tr><tr><td>Access<\/td><td>SSH as&nbsp;<code>ubuntu<\/code>&nbsp;with sudo; kubeconfig at&nbsp;<code>\/etc\/kubernetes\/admin.conf<\/code>&nbsp;(root only)<\/td><\/tr><tr><td>Namespace<\/td><td><code>nexus<\/code>&nbsp;(CM, PGWY and the database share it)<\/td><\/tr><tr><td>Package<\/td><td><code>certificate-manager-8.15.1-1.zip<\/code>&nbsp;from the Nexus Support Portal<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Package contents that matter:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">certificate-manager<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span><span class=\"hljs-number\">-1<\/span>\/\n  images\/cf-server-image<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span><span class=\"hljs-number\">-1.<\/span>tar      <span class=\"hljs-comment\"># Certificate Factory (CM server) image<\/span>\n  images\/pgw-image<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span><span class=\"hljs-number\">-1.<\/span>tar            <span class=\"hljs-comment\"># Protocol Gateway image<\/span>\n  deployment\/helm\/cf<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span>-SNAPSHOT<span class=\"hljs-number\">-1.<\/span>tgz           <span class=\"hljs-comment\"># base chart<\/span>\n  deployment\/helm\/cf-example<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span>-SNAPSHOT<span class=\"hljs-number\">-1.<\/span>tgz   <span class=\"hljs-comment\"># configurable parent chart (use this for CM)<\/span>\n  deployment\/helm\/pgw<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span>-SNAPSHOT<span class=\"hljs-number\">-1.<\/span>tgz          <span class=\"hljs-comment\"># base chart<\/span>\n  deployment\/helm\/pgw-example<span class=\"hljs-number\">-8.15<\/span><span class=\"hljs-number\">.1<\/span>-SNAPSHOT<span class=\"hljs-number\">-1.<\/span>tgz  <span class=\"hljs-comment\"># configurable parent chart (use this for PGWY)<\/span>\n  install\/PostgreSQL\/{create_db_and_role.sql,create_cmdb.sql,add_bootca.sql}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Run kubectl\/helm on the control-plane node as root (<code>sudo -i<\/code>) or prefix with&nbsp;<code>sudo<\/code>, and&nbsp;<code>export KUBECONFIG=\/etc\/kubernetes\/admin.conf<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Prerequisites checklist<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A working cluster:\u00a0<code>kubectl get nodes<\/code>\u00a0shows all nodes\u00a0<code>Ready<\/code>.<\/li>\n\n\n\n<li><strong>A ReadWriteMany (RWX) StorageClass.<\/strong>\u00a0CF&#8217;s shared volumes are RWX. A cluster with only\u00a0<code>local-path<\/code>\u00a0is not enough (see step 3).<\/li>\n\n\n\n<li><strong>A PostgreSQL database<\/strong>\u00a0reachable from the cluster (step 4).<\/li>\n\n\n\n<li><strong>Container images available to every node<\/strong>\u00a0(step 5). The images are not public.<\/li>\n\n\n\n<li><strong>A CM license file<\/strong>\u00a0(<code>*.license<\/code>) from Nexus (step 9).<\/li>\n\n\n\n<li>Helm 3.6.2+ (step 2).<\/li>\n\n\n\n<li><strong>Working pod-to-pod networking between nodes<\/strong>\u00a0(see Troubleshooting #1). Check it now:<code><em># from the control-plane: ping a pod that runs on the worker<\/em> kubectl get pods -A -o wide | grep &lt;worker-node-name> <em># pick any pod IP<\/em> ping -c2 &lt;that-pod-ip><\/code><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Install Helm (if missing)<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">curl -sSL https:<span class=\"hljs-comment\">\/\/get.helm.sh\/helm-v3.17.3-linux-amd64.tar.gz | tar xz -C \/tmp<\/span>\nsudo install \/tmp\/linux-amd64\/helm \/usr\/local\/bin\/helm\nhelm version --short\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. RWX storage (NFS) &#8211; lab setup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Skip this if you already have an RWX CSI class (EFS, Azure Files, CephFS&#8230;). For a lab, an NFS export on the control-plane node plus the NFS subdir provisioner is enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>On the control-plane node:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">sudo apt-get install -y nfs-kernel-server\nsudo mkdir -p \/srv\/nfs\/k8s &amp;&amp; sudo chmod <span class=\"hljs-number\">777<\/span> \/srv\/nfs\/k8s\n<span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"\/srv\/nfs\/k8s 172.31.0.0\/16(rw,sync,no_subtree_check,no_root_squash)\"<\/span> | sudo tee -a \/etc\/exports\nsudo exportfs -ra &amp;&amp; sudo systemctl enable --now nfs-kernel-server\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Adjust&nbsp;<code>172.31.0.0\/16<\/code>&nbsp;to your node subnet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>On every other node:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">sudo apt-<span class=\"hljs-keyword\">get<\/span> install -y nfs-common\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Install the provisioner<\/strong>&nbsp;(use the control-plane node&#8217;s private IP for&nbsp;<code>nfs.server<\/code>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">helm repo add nfs-subdir https:<span class=\"hljs-comment\">\/\/kubernetes-sigs.github.io\/nfs-subdir-external-provisioner\/<\/span>\nhelm repo update\nhelm install nfs-provisioner nfs-subdir\/nfs-subdir-external-provisioner \\\n  -n nfs-provisioner --create-namespace \\\n  --<span class=\"hljs-keyword\">set<\/span> nfs.server=172.31.10.122 \\\n  --<span class=\"hljs-keyword\">set<\/span> nfs.path=\/srv\/nfs\/k8s \\\n  --<span class=\"hljs-keyword\">set<\/span> storageClass.name=nfs-rwx \\\n  --<span class=\"hljs-keyword\">set<\/span> storageClass.defaultClass=false \\\n  --<span class=\"hljs-keyword\">set<\/span> storageClass.reclaimPolicy=Retain \\\n  --wait\nkubectl <span class=\"hljs-keyword\">get<\/span> sc nfs-rwx\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">NFS on a cluster node is for labs only. Use a managed RWX service in production.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Create the namespace:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">kubectl create <span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">nexus<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CM needs a database&nbsp;<code>cmdb<\/code>&nbsp;and a user&nbsp;<code>lcmreq<\/code>. For a lab, run PostgreSQL in the cluster. For production use a managed\/HA database and skip the StatefulSet below (run only the SQL).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 Prepare the SQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Copy the three scripts from&nbsp;<code>install\/PostgreSQL\/<\/code>&nbsp;to the control-plane node.&nbsp;<strong>Change the default password<\/strong>&nbsp;in&nbsp;<code>create_db_and_role.sql<\/code>&nbsp;(<code>REQreq01#<\/code>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">LCM_PW=$(openssl rand -hex <span class=\"hljs-number\">10<\/span>)          &lt;em&gt;<span class=\"hljs-comment\"># keep this; it goes into the CM Helm values later&lt;\/em&gt;<\/span>\nsed -i <span class=\"hljs-string\">\"s\/REQreq01#\/$LCM_PW\/\"<\/span> create_db_and_role.sql\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">4.2 Deploy PostgreSQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>postgres.yaml<\/code>&nbsp;(the Service is named&nbsp;<code>cmdb<\/code>, matching the chart&#8217;s default JDBC host):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">apiVersion: v1\n<span class=\"hljs-attr\">kind<\/span>: Secret\n<span class=\"hljs-attr\">metadata<\/span>: {<span class=\"hljs-attr\">name<\/span>: postgres-auth, <span class=\"hljs-attr\">namespace<\/span>: nexus}\n<span class=\"hljs-attr\">stringData<\/span>:\n  POSTGRES_PASSWORD: <span class=\"hljs-string\">\"&lt;superuser-password&gt;\"<\/span>\n---\napiVersion: apps\/v1\n<span class=\"hljs-attr\">kind<\/span>: StatefulSet\n<span class=\"hljs-attr\">metadata<\/span>: {<span class=\"hljs-attr\">name<\/span>: cmdb, <span class=\"hljs-attr\">namespace<\/span>: nexus}\n<span class=\"hljs-attr\">spec<\/span>:\n  serviceName: cmdb\n  <span class=\"hljs-attr\">replicas<\/span>: <span class=\"hljs-number\">1<\/span>\n  <span class=\"hljs-attr\">selector<\/span>: {<span class=\"hljs-attr\">matchLabels<\/span>: {<span class=\"hljs-attr\">app<\/span>: cmdb}}\n  <span class=\"hljs-attr\">template<\/span>:\n    metadata: {<span class=\"hljs-attr\">labels<\/span>: {<span class=\"hljs-attr\">app<\/span>: cmdb}}\n    <span class=\"hljs-attr\">spec<\/span>:\n      containers:\n      - name: postgres\n        <span class=\"hljs-attr\">image<\/span>: postgres:<span class=\"hljs-number\">16<\/span>\n        <span class=\"hljs-attr\">envFrom<\/span>: &#91;{<span class=\"hljs-attr\">secretRef<\/span>: {<span class=\"hljs-attr\">name<\/span>: postgres-auth}}]\n        <span class=\"hljs-attr\">env<\/span>: &#91;{<span class=\"hljs-attr\">name<\/span>: PGDATA, <span class=\"hljs-attr\">value<\/span>: <span class=\"hljs-regexp\">\/var\/<\/span>lib\/postgresql\/data\/pgdata}]\n        <span class=\"hljs-attr\">ports<\/span>: &#91;{<span class=\"hljs-attr\">containerPort<\/span>: <span class=\"hljs-number\">5432<\/span>}]\n        <span class=\"hljs-attr\">readinessProbe<\/span>: {<span class=\"hljs-attr\">exec<\/span>: {<span class=\"hljs-attr\">command<\/span>: &#91;<span class=\"hljs-string\">\"pg_isready\"<\/span>,<span class=\"hljs-string\">\"-U\"<\/span>,<span class=\"hljs-string\">\"postgres\"<\/span>]}, <span class=\"hljs-attr\">periodSeconds<\/span>: <span class=\"hljs-number\">5<\/span>}\n        <span class=\"hljs-attr\">volumeMounts<\/span>: &#91;{<span class=\"hljs-attr\">name<\/span>: data, <span class=\"hljs-attr\">mountPath<\/span>: <span class=\"hljs-regexp\">\/var\/<\/span>lib\/postgresql\/data}]\n        <span class=\"hljs-attr\">resources<\/span>: {<span class=\"hljs-attr\">requests<\/span>: {<span class=\"hljs-attr\">cpu<\/span>: <span class=\"hljs-number\">100<\/span>m, <span class=\"hljs-attr\">memory<\/span>: <span class=\"hljs-number\">256<\/span>Mi}}\n  <span class=\"hljs-attr\">volumeClaimTemplates<\/span>:\n  - metadata: {<span class=\"hljs-attr\">name<\/span>: data}\n    <span class=\"hljs-attr\">spec<\/span>:\n      accessModes: &#91;ReadWriteOnce]\n      <span class=\"hljs-attr\">storageClassName<\/span>: local-path\n      <span class=\"hljs-attr\">resources<\/span>: {<span class=\"hljs-attr\">requests<\/span>: {<span class=\"hljs-attr\">storage<\/span>: <span class=\"hljs-number\">5<\/span>Gi}}\n---\napiVersion: v1\n<span class=\"hljs-attr\">kind<\/span>: Service\n<span class=\"hljs-attr\">metadata<\/span>: {<span class=\"hljs-attr\">name<\/span>: cmdb, <span class=\"hljs-attr\">namespace<\/span>: nexus}\n<span class=\"hljs-attr\">spec<\/span>:\n  selector: {<span class=\"hljs-attr\">app<\/span>: cmdb}\n  <span class=\"hljs-attr\">ports<\/span>: &#91;{<span class=\"hljs-attr\">port<\/span>: <span class=\"hljs-number\">5432<\/span>, <span class=\"hljs-attr\">targetPort<\/span>: <span class=\"hljs-number\">5432<\/span>}]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">kubectl apply -f postgres.yaml\nkubectl -n nexus rollout status statefulset\/cmdb\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">4.3 Load the schema<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">K=<span class=\"hljs-string\">\"kubectl -n nexus\"<\/span>\n$K exec -i cmdb<span class=\"hljs-number\">-0<\/span> -- psql -X -v ON_ERROR_STOP=<span class=\"hljs-number\">1<\/span> -U postgres -d postgres &lt; create_db_and_role.sql\n$K exec -i cmdb<span class=\"hljs-number\">-0<\/span> -- psql -X -v ON_ERROR_STOP=<span class=\"hljs-number\">1<\/span> -q -U postgres -d cmdb &lt; create_cmdb.sql\n$K exec -i cmdb<span class=\"hljs-number\">-0<\/span> -- psql -X -v ON_ERROR_STOP=<span class=\"hljs-number\">1<\/span> -q -U postgres -d cmdb &lt; add_bootca.sql\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<ul class=\"wp-block-list\">\n<li><code>create_cmdb.sql<\/code>\u00a0creates the tables and grants access to\u00a0<code>lcmreq<\/code>.<\/li>\n\n\n\n<li><code>add_bootca.sql<\/code>\u00a0loads the\u00a0<strong>vendor Boot CA<\/strong>\u00a0used for bootstrapping. It matches\u00a0<code>bootca.cer<\/code>\u00a0and\u00a0<code>boot_vro.p12<\/code>\u00a0shipped in the example charts. It is a well-known sample CA: it must be replaced during CM bootstrap before production use (step 11).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">kubectl -n nexus exec cmdb-0 -- psql -U postgres -d cmdb -c \"\\dt\" | tail -3      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># ~46 tables<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nkubectl -n nexus exec cmdb-0 -- psql -U postgres -d cmdb -tc \"select objectid from adminstore\"\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># expected: c-BootCA, k-BootCAkey<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Load the container images on every node<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The images are in the package as tar files, and the Helm values use&nbsp;<code>pullPolicy: IfNotPresent<\/code>, so importing them into containerd&#8217;s&nbsp;<code>k8s.io<\/code>&nbsp;namespace on&nbsp;<strong>every node<\/strong>&nbsp;is enough (no registry needed for a lab).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># on each node<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nsudo ctr -n k8s.io images import cf-server-image-8.15.1-1.tar\nsudo ctr -n k8s.io images import pgw-image-8.15.1-1.tar\nsudo ctr -n k8s.io images ls -q | grep smartid\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># docker.io\/smartid\/certificatemanager\/cf-server:8.15.1-1<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># docker.io\/smartid\/certificatemanager\/pgw:8.15.1-1<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Tips:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The tars are ~650 MB. Over a slow link,\u00a0<code>scp<\/code>\u00a0them to\u00a0<strong>one<\/strong>\u00a0node, then\u00a0<code>scp<\/code>\u00a0from that node to the others over the private network.<\/li>\n\n\n\n<li>For production, push to a private registry and set\u00a0<code>image.repository<\/code>\u00a0plus\u00a0<code>imagePullSecrets<\/code>\u00a0instead. Nexus does not allow publishing CM images publicly.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Extract the Helm charts<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">mkdir -p ~\/nexus-helm &amp;&amp; cd ~\/nexus-helm\nfor f in \/path\/to\/deployment\/helm\/*.tgz; do tar xzf \"$f\"; done\nls     <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># cf  cf-example  pgw  pgw-example<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>cf-example<\/code>&nbsp;and&nbsp;<code>pgw-example<\/code>&nbsp;are the charts you configure. They already contain the&nbsp;<code>cf<\/code>&nbsp;\/&nbsp;<code>pgw<\/code>&nbsp;base charts under&nbsp;<code>charts\/<\/code>&nbsp;and the example config files under&nbsp;<code>files\/<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. Install Certificate Manager<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">7.1 Values file (<code>cm-values.yaml<\/code>)<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">config:\n  database:\n    name: \"jdbc:postgresql:\/\/cmdb.nexus.svc.cluster.local:5432\/cmdb\"\n    user: \"lcmreq\"\n    password: \"<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LCM_PW<\/span> <span class=\"hljs-attr\">from<\/span> <span class=\"hljs-attr\">step<\/span> <span class=\"hljs-attr\">4.1<\/span>&gt;<\/span>\"\n    connections: \"20\"\ncf:\n  replicaCount: 1                 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># chart default is 2; 1 suits small lab nodes<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n  image:\n    repository: smartid\/certificatemanager\/cf-server\n    tag: 8.15.1-1\n    pullPolicy: IfNotPresent\n  pvc:\n    cf:\n      storageClassName: nfs-rwx   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># must support ReadWriteMany<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n      size: 200Mi\n    cis:\n      storageClassName: nfs-rwx\n      size: 100Mi\n  configStatePvc:\n    storageClassName: local-path  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># chart default is \"default\"; this cluster has no default class<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Notes on what we changed from the chart defaults:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>database.name<\/code>\/<code>password<\/code>: point at our PostgreSQL and use the non-default password.<\/li>\n\n\n\n<li><code>pvc.*.storageClassName<\/code>: chart default is\u00a0<code>azurefile-csi<\/code>\u00a0(Azure only).<\/li>\n\n\n\n<li><code>configStatePvc.storageClassName<\/code>: chart default is\u00a0<code>default<\/code>; if your cluster has no default StorageClass the PVC stays\u00a0<code>Pending<\/code>. Use an RWO class that exists.<\/li>\n\n\n\n<li>The password is passed as a plain Helm value. It is stored in the Helm release secret. For production inject it more securely.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">7.2 Place the license&nbsp;<strong>(required, see step 9)<\/strong><\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">cp \/path\/to\/<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">your<\/span>&gt;<\/span>.license ~\/nexus-helm\/cf-example\/files\/license\/\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The chart builds the secret&nbsp;<code>nexus-cf-license-secret<\/code>&nbsp;from&nbsp;<code>files\/license\/*.license<\/code>. The file&nbsp;<strong>must end in&nbsp;<code>.license<\/code><\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7.3 Install<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">cd ~\/nexus-helm\/cf-example\nhelm dependency build\nhelm template cm . -n nexus -f ..\/cm-values.yaml &gt; \/tmp\/cm-rendered.yaml    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># optional dry check<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nhelm install cm . -n nexus -f ..\/cm-values.yaml --wait --timeout 15m\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Release name&nbsp;<code>cm<\/code>&nbsp;produces the StatefulSet and Service&nbsp;<strong><code>cm-cf<\/code><\/strong>&nbsp;(port 5009). PGWY&#8217;s default&nbsp;<code>cmhost: cm-cf<\/code>&nbsp;relies on this name, so keep the release name&nbsp;<code>cm<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7.4 Check<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">kubectl -n nexus <span class=\"hljs-keyword\">get<\/span> pods,pvc,svc\nkubectl -n nexus logs cm-cf-0 -c cf --tail=50\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Expected once licensed:&nbsp;<code>cm-cf-0<\/code>&nbsp;is&nbsp;<code>1\/1 Running<\/code>; PVCs&nbsp;<code>cf<\/code>&nbsp;and&nbsp;<code>cis-keystore<\/code>&nbsp;are&nbsp;<code>Bound<\/code>&nbsp;(RWX),&nbsp;<code>configstate-cm-cf-0<\/code>&nbsp;is&nbsp;<code>Bound<\/code>&nbsp;(RWO).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. Install Protocol Gateway<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">8.1 Create a TLS keystore for Tomcat (<strong>required, not in the package<\/strong>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PGW&#8217;s&nbsp;<code>server.xml<\/code>&nbsp;expects&nbsp;<code>\/opt\/tomcat\/conf\/tomcat-tls.p12<\/code>&nbsp;(password&nbsp;<code>1234<\/code>), delivered through the&nbsp;<code>files\/tomcat\/<\/code>&nbsp;directory of the chart. Without it, Tomcat fails with&nbsp;<code>FileNotFoundException: \/opt\/tomcat\/conf\/tomcat-tls.p12<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lab self-signed certificate:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">cd ~<span class=\"hljs-regexp\">\/nexus-helm\/<\/span>pgw-example\nopenssl req -x509 -newkey rsa:<span class=\"hljs-number\">2048<\/span> -nodes -days <span class=\"hljs-number\">825<\/span> \\\n  -keyout \/tmp\/tls.key -out \/tmp\/tls.crt \\\n  -subj <span class=\"hljs-string\">\"\/CN=pgw.nexus.svc.cluster.local\/O=Nexus Lab\"<\/span> \\\n  -addext <span class=\"hljs-string\">\"subjectAltName=DNS:pgw,DNS:pgw.nexus.svc.cluster.local,DNS:localhost\"<\/span>\nopenssl pkcs12 -<span class=\"hljs-keyword\">export<\/span> -<span class=\"hljs-keyword\">in<\/span> \/tmp\/tls.crt -inkey \/tmp\/tls.key -name tomcat \\\n  -out files\/tomcat\/tomcat-tls.p12 -passout pass:<span class=\"hljs-number\">1234<\/span>\nrm -f \/tmp\/tls.key \/tmp\/tls.crt\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For production use a certificate from your CA with the real DNS names, and change the keystore password in&nbsp;<code>server.xml<\/code>&nbsp;as well.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8.2 Values file (<code>pgw-values.yaml<\/code>)<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">cmhost: cm-cf          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># service name of CF; the chart writes this into cm-gateway.properties<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\npgw:\n  replicaCount: 1\n  image:\n    repository: smartid\/certificatemanager\/pgw\n    tag: 8.15.1-1\n    pullPolicy: IfNotPresent\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">8.3 Install<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">cd ~<span class=\"hljs-regexp\">\/nexus-helm\/<\/span>pgw-example\nhelm dependency build\nhelm install pgw . -n nexus -f ..\/pgw-values.yaml\nkubectl -n nexus <span class=\"hljs-keyword\">get<\/span> pods,svc\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The Service&nbsp;<code>pgw<\/code>&nbsp;exposes&nbsp;<strong>8080<\/strong>&nbsp;(HTTP \/ health, do not publish),&nbsp;<strong>8443<\/strong>&nbsp;(TLS) and&nbsp;<strong>8444<\/strong>&nbsp;(mutual TLS).<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>PGW needs CF running.<\/strong>&nbsp;At start-up it connects to CM (<code>cm-cf<\/code>). If CF is down or the name does not resolve, PGW exits with&nbsp;<code>Start failed: Protocol Gateway\/Ping bad configuration<\/code>. Always bring CM up first. After CF is healthy, restart PGW:&nbsp;<code>kubectl -n nexus delete pod pgw-0<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">If you change anything under&nbsp;<code>files\/<\/code>&nbsp;and run&nbsp;<code>helm upgrade<\/code>, delete the pod afterwards so it re-reads the ConfigMaps.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">9. License &#8211; PENDING<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CF logs on every start:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">license  critical  No license files were found in directory \/opt\/cm\/server\/license.\noper     critical  Startup Failure: License check failed ... License directory contains no .license files\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">CF checks the license directory at every start and refuses to run without a valid file. The license is issued by Nexus and is tied to your organisation and the products\/modules you bought. It is not included in the install package and cannot be generated locally.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.1 Obtain the license<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Sign in to the Nexus Support Portal (<a href=\"https:\/\/support.nexusgroup.com\/\">https:\/\/support.nexusgroup.com\/<\/a>) with your customer account, or ask your Nexus account manager \/ support contact.<\/li>\n\n\n\n<li>Request (or download) the\u00a0<strong>Certificate Manager 8.15<\/strong>\u00a0license for your installation. Give them what they ask for (typically customer name and the environment: test or production).<\/li>\n\n\n\n<li>You receive one or more files ending in\u00a0<strong><code>.license<\/code><\/strong>. Keep the original safe.<\/li>\n\n\n\n<li>Confirm it covers what you will use: the CM server, and the PGWY protocols you plan to enable (for example ACME, EST, SCEP). A missing module shows up later as a disabled servlet, not at start-up.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">9.2 Apply it with Helm (recommended)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The chart builds the secret&nbsp;<code>nexus-cf-license-secret<\/code>&nbsp;from&nbsp;<code>cf-example\/files\/license\/*.license<\/code>. The file name&nbsp;<strong>must end in&nbsp;<code>.license<\/code><\/strong>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;em&gt;<span class=\"hljs-comment\"># on the control-plane node&lt;\/em&gt;<\/span>\ncp \/path\/to\/&lt;your&gt;.license ~\/nexus-helm\/cf-example\/files\/license\/\nls -l ~\/nexus-helm\/cf-example\/files\/license\/        &lt;em&gt;<span class=\"hljs-comment\"># readme.md + your .license&lt;\/em&gt;<\/span>\n\ncd ~\/nexus-helm\/cf-example\nhelm upgrade cm . -n nexus -f ..\/cm-values.yaml\n\n&lt;em&gt;<span class=\"hljs-comment\"># confirm the secret now contains the file&lt;\/em&gt;<\/span>\nkubectl -n nexus get secret nexus-cf-license-secret -o jsonpath=<span class=\"hljs-string\">'{.data}'<\/span> | grep -o <span class=\"hljs-string\">'\"&#91;^\"]*\\.license\"'<\/span>\n\n&lt;em&gt;<span class=\"hljs-comment\"># restart so the pod re-mounts the secret&lt;\/em&gt;<\/span>\nkubectl -n nexus delete pod cm-cf<span class=\"hljs-number\">-0<\/span>\nkubectl -n nexus logs -f cm-cf<span class=\"hljs-number\">-0<\/span> -c cf\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Healthy start-up: the&nbsp;<code>License check failed<\/code>&nbsp;\/&nbsp;<code>Startup Failure<\/code>&nbsp;lines disappear, CF reports that the license was loaded, connects to the database, and the pod becomes&nbsp;<code>1\/1 Running<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.3 Alternative: create the secret directly (keeps the file out of the chart directory)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Helm always renders the secret from&nbsp;<code>files\/license\/<\/code>, so if you prefer not to store the license in the chart folder, copy the file in only for the&nbsp;<code>helm upgrade<\/code>, then remove it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">cp \/path\/to\/<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">your<\/span>&gt;<\/span>.license ~\/nexus-helm\/cf-example\/files\/license\/\nhelm upgrade cm . -n nexus -f ..\/cm-values.yaml\nrm ~\/nexus-helm\/cf-example\/files\/license\/*.license\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">(Later upgrades re-render the secret from the folder, so the file must be present again for every&nbsp;<code>helm upgrade<\/code>, otherwise the secret is emptied and CF fails on its next restart.)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.4 Renewal or change of license<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Repeat 9.2 with the new file (remove the old one first if it should be replaced rather than added). Always restart the CF pod afterwards. Check expiry dates with Nexus support; there is no warning if a license is about to lapse other than CF log messages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.5 License troubleshooting<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Log message \/ symptom<\/th><th class=\"has-text-align-left\" data-align=\"left\">Meaning<\/th><th class=\"has-text-align-left\" data-align=\"left\">Fix<\/th><\/tr><\/thead><tbody><tr><td><code>No license files were found in directory \/opt\/cm\/server\/license<\/code><\/td><td>Secret is empty or the file doesn&#8217;t end in&nbsp;<code>.license<\/code><\/td><td>Rename, re-run&nbsp;<code>helm upgrade<\/code>, restart pod<\/td><\/tr><tr><td>Same message after&nbsp;<code>helm upgrade<\/code><\/td><td>Old pod still running with the old secret<\/td><td><code>kubectl -n nexus delete pod cm-cf-0<\/code><\/td><\/tr><tr><td>License rejected \/ invalid \/ expired<\/td><td>File is for another product\/version or has expired<\/td><td>Request a new file from Nexus<\/td><\/tr><tr><td>CF runs but a PGWY protocol does nothing<\/td><td>License lacks that module<\/td><td>Ask Nexus to extend the license<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Never share the license file publicly or commit it to a repository.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">10. Verification&nbsp;<strong>(untested &#8211; needs the license)<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">kubectl -n nexus get pods                     <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># cmdb-0, cm-cf-0, pgw-0 all Running\/Ready<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nhelm list -n nexus\nkubectl -n nexus logs cm-cf-0 -c cf --tail=100\nkubectl -n nexus logs pgw-0 --tail=100\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Reach PGW from your workstation:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">kubectl -n nexus port-forward svc\/pgw 8080:8080 8443:8443 8444:8444\ncurl -i http:\/\/localhost:8080\/health\ncurl -ik https:\/\/localhost:8443\/        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># -k: lab certificate<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Reach CF with Nexus CM client tools (controlled testing only):<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">kubectl -n nexus port-forward svc\/cm-cf 5009:5009\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">11. Bootstrap CM&nbsp;<strong>(untested)<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A fresh install trusts the&nbsp;<strong>vendor sample Boot CA<\/strong>. Before production, follow Nexus&#8217;s &#8220;Bootstrap Certificate Manager&#8221; procedure for 8.15 (Nexus documentation site): create your own Issuing CA and officers, replace the bootstrap identity, and revoke the Boot CA once the replacement is verified. Helm rollback does&nbsp;<strong>not<\/strong>&nbsp;undo database changes or PKI key state, so take a database backup first.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">12. Enable PGWY protocols&nbsp;<strong>(untested)<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The protocol servlets (ACME, EST, SCEP, CMP, REST&#8230;) are installed but off by default. A running pod does not mean enrollment works. Edit the matching file in&nbsp;<code>pgw-example\/files\/conf\/<\/code>&nbsp;(for example&nbsp;<code>acme.properties<\/code>:&nbsp;<code>start = true<\/code>),&nbsp;<code>helm upgrade pgw<\/code>, and delete the pod. Each protocol also needs a matching procedure\/profile in CM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For production access, expose PGW through a layer-4 (TCP) load balancer if you need TLS client-certificate authentication, because an HTTP-terminating ingress breaks mutual TLS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Out of scope here:<\/em>&nbsp;Nexus Hermod (authentication service for the CM Web UI) and the CM Web UI (a separate WAR\/Tomcat install per the vendor) were not part of this task.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Completion checklist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tick these off in order. Items 1-6 were completed on the lab cluster; 7 onward need the license.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[x] 1. Cluster healthy, cross-node pod networking and DNS working (Troubleshooting #1)<\/li>\n\n\n\n<li>[x] 2. Helm installed;\u00a0<code>nfs-rwx<\/code>\u00a0StorageClass created; namespace\u00a0<code>nexus<\/code>\u00a0exists<\/li>\n\n\n\n<li>[x] 3. PostgreSQL running;\u00a0<code>cmdb<\/code>\u00a0schema and Boot CA loaded<\/li>\n\n\n\n<li>[x] 4. CF and PGW images imported on\u00a0<strong>every<\/strong>\u00a0node<\/li>\n\n\n\n<li>[x] 5.\u00a0<code>cm<\/code>\u00a0and\u00a0<code>pgw<\/code>\u00a0Helm releases installed (<code>helm list -n nexus<\/code>\u00a0shows\u00a0<code>deployed<\/code>)<\/li>\n\n\n\n<li>[x] 6.\u00a0<code>tomcat-tls.p12<\/code>\u00a0created for PGW<\/li>\n\n\n\n<li>[ ] 7.\u00a0<strong>License file obtained and applied (step 9)<\/strong><\/li>\n\n\n\n<li>[ ] 8.\u00a0<code>cm-cf-0<\/code>\u00a0is\u00a0<code>1\/1 Running<\/code><\/li>\n\n\n\n<li>[ ] 9.\u00a0<code>pgw-0<\/code>\u00a0restarted and\u00a0<code>1\/1 Running<\/code>;\u00a0<code>curl http:\/\/localhost:8080\/health<\/code>\u00a0returns 200 via port-forward<\/li>\n\n\n\n<li>[ ] 10. CM bootstrap done, Boot CA replaced (step 11)<\/li>\n\n\n\n<li>[ ] 11. At least one PGWY protocol enabled and a test certificate issued (step 12)<\/li>\n\n\n\n<li>[ ] 12. Passwords rotated, database backup scheduled, monitoring in place<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting (all hit during this deployment)<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">#<\/th><th class=\"has-text-align-left\" data-align=\"left\">Symptom<\/th><th class=\"has-text-align-left\" data-align=\"left\">Cause<\/th><th class=\"has-text-align-left\" data-align=\"left\">Fix<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>PGW:&nbsp;<code>UnknownHostException: cm-cf<\/code>&nbsp;\/&nbsp;<code>Temporary failure in name resolution<\/code>; pods on one node cannot reach CoreDNS;&nbsp;<code>ping<\/code>&nbsp;to a pod on the other node gets 100% loss<\/td><td>Cross-node pod traffic dropped. Calico was routing without encapsulation (<code>ipipMode: Never<\/code>,&nbsp;<code>vxlanMode: Never<\/code>), which on AWS requires&nbsp;<strong>source\/destination check disabled<\/strong>&nbsp;on every EC2 node<\/td><td>Disable source\/dest check on all nodes (AWS),&nbsp;<strong>or<\/strong>&nbsp;enable IPIP\/VXLAN on the Calico IPPool. After the fix,&nbsp;<code>ping<\/code>&nbsp;a pod on the other node and&nbsp;<code>nslookup cm-cf.nexus.svc.cluster.local<\/code>&nbsp;from a pod on each node<\/td><\/tr><tr><td>2<\/td><td><code>cm-cf-0<\/code>&nbsp;CrashLoopBackOff:&nbsp;<code>License directory contains no .license files<\/code><\/td><td>No license file in&nbsp;<code>cf-example\/files\/license\/<\/code><\/td><td>Step 9<\/td><\/tr><tr><td>3<\/td><td><code>pgw-0<\/code>&nbsp;fails:&nbsp;<code>FileNotFoundException: \/opt\/tomcat\/conf\/tomcat-tls.p12<\/code><\/td><td>Keystore not shipped in the package<\/td><td>Step 8.1<\/td><\/tr><tr><td>4<\/td><td>PVC&nbsp;<code>Pending<\/code>&nbsp;for&nbsp;<code>cf<\/code>&nbsp;\/&nbsp;<code>cis-keystore<\/code><\/td><td>StorageClass has no RWX (e.g.&nbsp;<code>local-path<\/code>)<\/td><td>Step 3 \/ set&nbsp;<code>cf.pvc.*.storageClassName<\/code><\/td><\/tr><tr><td>5<\/td><td>PVC&nbsp;<code>configstate-cm-cf-0<\/code>&nbsp;<code>Pending<\/code><\/td><td><code>configStatePvc.storageClassName: default<\/code>&nbsp;but no default class<\/td><td>Set it to an existing RWO class<\/td><\/tr><tr><td>6<\/td><td><code>ImagePullBackOff<\/code>&nbsp;\/&nbsp;<code>ErrImagePull<\/code><\/td><td>Image not on that node, or tag mismatch<\/td><td>Import on&nbsp;<strong>every<\/strong>&nbsp;node (step 5); names must be&nbsp;<code>smartid\/certificatemanager\/{cf-server,pgw}:8.15.1-1<\/code><\/td><\/tr><tr><td>7<\/td><td><code>scp<\/code>&nbsp;of the image tars takes forever<\/td><td>Slow uplink<\/td><td>Copy once, relay node-to-node<\/td><\/tr><tr><td>8<\/td><td>CF restarts but ignores a new license<\/td><td>Pod still has the old secret<\/td><td><code>kubectl -n nexus delete pod cm-cf-0<\/code>&nbsp;after&nbsp;<code>helm upgrade<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Useful commands:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">kubectl -n nexus describe pod <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pod<\/span>&gt;<\/span>\nkubectl -n nexus logs <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pod<\/span>&gt;<\/span> --previous\nkubectl -n nexus get events --sort-by=.lastTimestamp | tail -20\nhelm history cm -n nexus ; helm history pgw -n nexus\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Uninstall<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">helm uninstall pgw -n nexus\nhelm uninstall cm  -n nexus\nkubectl -n nexus delete statefulset cmdb svc\/cmdb      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># database<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nkubectl -n nexus delete pvc --all                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># destroys data<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Security notes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Do not leave SSH passwords in scripts or chat. Use SSH keys and rotate any shared password.<\/li>\n\n\n\n<li>Change every default:\u00a0<code>lcmreq<\/code>\u00a0password, Tomcat keystore password (<code>1234<\/code>), the sample Boot CA, truststore password (<code>123456<\/code>\u00a0in\u00a0<code>server.xml<\/code>).<\/li>\n\n\n\n<li>Helm values containing passwords end up in Helm release secrets; restrict access to the\u00a0<code>nexus<\/code>\u00a0namespace.<\/li>\n\n\n\n<li>The NFS export uses\u00a0<code>no_root_squash<\/code>\u00a0and open permissions: lab only.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Step-by-step tutorial, written from an actual deployment of&nbsp;Certificate Manager 8.15.1-1&nbsp;(CF server image&nbsp;8.15.1-1, PGW image&nbsp;8.15.1-1) on a 2-node kubeadm cluster. Status of this document (2026-11-12)&nbsp;Steps 1-8 were executed&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-79854","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79854","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=79854"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79854\/revisions"}],"predecessor-version":[{"id":79855,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79854\/revisions\/79855"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=79854"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=79854"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=79854"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}