DaShaun https://dashaun.com/ Recent content on DaShaun Hugo -- gohugo.io en-us Mon, 24 Nov 2025 14:19:27 -0600 Offstage Advocacy https://dashaun.com/posts/offstage-advocacy/ Mon, 24 Nov 2025 14:19:27 -0600 https://dashaun.com/posts/offstage-advocacy/ <h2 id="the-essential-role-of-offstage-advocacy-in-developer-relations"> The Essential Role of Offstage Advocacy in Developer Relations <a class="heading-link" href="#the-essential-role-of-offstage-advocacy-in-developer-relations"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p>The Developer Advocate (DA) title is notoriously inconsistent. Before I took this role, I interviewed dozens of people in the developer advocate space.</p> <ul> <li>What does it mean to be a developer advocate?</li> <li>How do you do it well?</li> <li>What does good look like?</li> <li>Where does it report: engineering, marketing, sales?</li> </ul> <p>The only thing that was consistent, is that there was zero consistency, even within the same organization. Every DA had a different approach. Every DA manager had a different way to measure success. Those interviews left me with even more questions. They also taught me one very important thing.</p> <p>There&rsquo;s more to developer advocacy than just conferences and content. The real thing that companies want, is they want to see it move the needle. They want to see us driving adoption, or driving sales, or driving perceptions, or sentiment. We&rsquo;ve got to move the needle.</p> <p>So, how do we do that when we aren&rsquo;t on stage?</p> <p>I&rsquo;ve coined a new term for the essential work that happens outside the spotlight: <strong>Offstage Advocacy.</strong></p> <h3 id="what-is-offstage-advocacy"> What is Offstage Advocacy? <a class="heading-link" href="#what-is-offstage-advocacy"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>Offstage Advocacy is the essential phrase for what we are doing when we&rsquo;re not on stage, when we&rsquo;re not publishing content. It&rsquo;s all the things that we have to do in order to move the needle, while not being in public.</p> <p>It’s going and meeting our customers where they’re at, understanding how they’re using the thing that we’re advocating for. <em>How are they using it? What do they wish it did better? What are their pain points?</em></p> <p>Part of developer advocacy is understanding the community. But if all we&rsquo;re doing is publishing content on YouTube, or a blog, and speaking at a conference, how are we getting that real feedback? We have found getting real answers from real conversations with customers: it can’t be done at a conference or in the comments sections. It has to be on their terms. We have to meet our customers where they&rsquo;re at, in a <em>safe</em> space.</p> <h3 id="the-budget-reality-why-i-needed-another-avenue"> The Budget Reality: Why I Needed Another Avenue <a class="heading-link" href="#the-budget-reality-why-i-needed-another-avenue"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>What most of my audience doesn&rsquo;t realize is that most developer advocates are on a very tight budget. They see a DA on stage at their favorite conference, and on YouTube, and assume that is what DAs do all year.</p> <p>After my first full year of applying to dozens of conferences, 50% of the applications getting rejected, and 90% of my accepted talks not getting budget, I realized that I needed another avenue to deliver value. <em>(I also had the world&rsquo;s best manager, Tasha Isenberg, pointing me in the right direction.)</em></p> <p>The current trend of the &ldquo;forward-deployed engineer&rdquo; really resonates with me, but with developer advocacy it has to be different. We realized that we could be a powerful &ldquo;voice of the customer&rdquo; because of the real relationships and insights we gain into real-world use cases. We realized that we could be &ldquo;advocating&rdquo; for our customers by taking their feedback directly to the product teams. That is the core value proposition we had to build.</p> <h3 id="the-offstage-pattern-weekly-office-hours"> The Offstage Pattern: Weekly &ldquo;office hours&rdquo; <a class="heading-link" href="#the-offstage-pattern-weekly-office-hours"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>We set up regularly scheduled weekly calls with customers. These calls are typically small groups, sometimes larger depending on the topic, and are scheduled for one hour. We actually have an agenda for it: News, Roadmap Updates, Demo/Presentation, and Q&amp;A.</p> <p>But we always start off by saying something similar:</p> <p><code>The agenda doesn't matter. If there is something pressing, a hot topic, or questions, we can start with Q&amp;A.</code></p> <ul> <li>Listening has a higher priority than presenting.</li> <li>Customers are not obligated to attend, its optional for them.</li> <li>Be prepared for zero people to show up, and don&rsquo;t take it personally, use that time to practice your demo or presentation anyway.</li> <li>Blocking time for a customer that never shows up is not delivering value.</li> </ul> <h3 id="scaling-the-impact-teamwork-and-visibility"> Scaling the Impact: Teamwork and Visibility <a class="heading-link" href="#scaling-the-impact-teamwork-and-visibility"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>This offstage advocacy isn&rsquo;t a &ldquo;standalone&rdquo; effort. It&rsquo;s hard to get started. We have to have people inside your organization that are willing to sponsor and collaborate on this effort. Even if we have a great relationship with a customer, we need to strengthen the relationship between the customer and the account teams.</p> <p><strong>The Shared Notes Channel:</strong> For every engagement, we take notes, providing a &ldquo;summary&rdquo; of how it went, no matter if it was positive or negative. We capture number of attendees, names, and team names. We needed a system, so we could look at our notes from week to week, as we go from customer to customer. We set up an internal channel for all of these &ldquo;office hours&rdquo; notes to be shared.</p> <p><strong>Unblocking the Organization:</strong> Because most organizations don&rsquo;t have enough developer advocates to meet with all of their customers every week (we don&rsquo;t either), this shared channel lets others see what is happening: Sales, Product, R&amp;D, Leadership.</p> <p><strong>Cross-Selling and Education:</strong> When we bring one of my amazing teammates (Josh, Dan, Cora or Coté) in to meet with a customer that I&rsquo;ve primarily been engaged with, we can bring them up to speed easily and completely. Additionally, this channel lets everyone see what people are presenting on. For example, when someone sees that a presentation on &ldquo;Spring AI&rdquo; had 1000 people attending at one of our customers, they ask themselves, &ldquo;I wonder if my customer would like a presentation on Spring AI?&rdquo; They know exactly who to go to. And because it&rsquo;s a regularly scheduled weekly meeting, hopefully/eventually, there will be an opening that works for the presenter and that customer!</p> <p><strong>Ownership and Feedback Loop:</strong> The account team has a pattern to follow (news/roadmap/demo/Q&amp;A) and topics to discuss from the channel, even if they don&rsquo;t have a DA. We&rsquo;ve been &ldquo;bootstrapping&rdquo; the weekly meetings, but eventually, it&rsquo;s the account team that &ldquo;owns&rdquo; those meetings. The more customers we get in front of, the more we learn about how the products are being used by our customers. This makes MORE account teams want to pull DAs into these &ldquo;offstage&rdquo; engagements.</p> <h3 id="creating-champions"> Creating Champions <a class="heading-link" href="#creating-champions"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>This approach has allowed us to find &ldquo;champions&rdquo; within our customers. We are having regularly scheduled meetings and building real relationships. So when it&rsquo;s time for our company/product to show up at a conference, it doesn&rsquo;t have to be one of our DAs. We have dozens of &ldquo;champions&rdquo; that we trust to put &ldquo;on stage&rdquo; at a conference to deliver a powerful, educational, real-world story. As a developer advocate, there are very few things more rewarding, than getting to put a customer on stage.</p> <p>Every company has a different definition, and way to measure developer advocates, but today, I&rsquo;m certain that every developer advocate should have at least one customer that they can practice <strong>Offstage Advocacy</strong> with, so they can get real feedback (at a minimum) and drive real outcomes (ideally).</p> <p>It&rsquo;s not onstage or offstage. I think it has to be both, given the current landscape. You still have to build your brand and be credible, but you&rsquo;ve also got to build relationships and be able to move the meter with customers.</p> <h4 id="additional-notes"> Additional Notes <a class="heading-link" href="#additional-notes"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h4> <p>I also realize how lucky I am to be an advocate for the world&rsquo;s best Java framework in Spring, and the world&rsquo;s best PaaS in Tanzu Platform. Until today, I have been struggling with how to name this thing that we have been having so much success with. We are constantly iterating on our process, and &ldquo;forward-deployed advocate&rdquo; didn&rsquo;t capture it. Naming things is hard. It was a conversation with my friend Joel this morning, that lead me to <strong>Offstage Advocacy</strong>. It feels perfect. It fits for every version that we have had before, and the future versions as far as I can see. I had to get it out of my head. I&rsquo;m looking forward to your feedback.</p> How to Deploy Korifi v0.16.0 on Single-Node K3s with Ubuntu 24.04 Raspberry Pi 5 (Step-by-Step) https://dashaun.com/posts/korifi-v0_16_0-on-raspberry-pi/ Mon, 22 Sep 2025 18:46:35 -0500 https://dashaun.com/posts/korifi-v0_16_0-on-raspberry-pi/ <h2 id="bill-of-materials-for-the-raspberry-pi-cluster"> Bill of Materials for the Raspberry Pi Cluster <a class="heading-link" href="#bill-of-materials-for-the-raspberry-pi-cluster"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <ul> <li>(1) Raspberry Pi 5 (8gb)</li> <li>(1) SanDisk 128gb</li> <li>(1) USB-C, right-angle, &ldquo;HotNow&rdquo; brand cables</li> <li>(1) 1-foot CAT6 flat ethernet cables</li> <li>(1) Ubiquiti Flex Mini switch</li> <li>(1) Official Raspberry Pi 5 Charger</li> <li>(1) Raspberry Pi 5 case</li> <li>(1) DeskPi 7.84-inch Touch Screen</li> </ul> <p>This is a slightly different setup than <a href="https://dashaun.com/posts/korifi-v0_14_0-on-raspberry-pi/" class="external-link" target="_blank" rel="noopener">the last time.</a> But only because my office is a mess, nothing else. Had zero power concerns or warnings.</p> <h2 id="installing-ubuntu-2404-on-raspberry-pi"> Installing Ubuntu 24.04 on Raspberry Pi <a class="heading-link" href="#installing-ubuntu-2404-on-raspberry-pi"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p>It&rsquo;s September 2025, why am I using Ubuntu 24.04 when I was using 24.10 last time? Since 25.04 is available, 24.10 is no longer a viable option. I tried with 25.04 but I ran into two unexpected problems.</p> <p>The first problem is that the cgroup settings that were <a href="https://dashaun.com/posts/korifi-on-raspberry-pi/" class="external-link" target="_blank" rel="noopener">handled</a> in 24.10, were not handled in 25.04. That was easy enough to work through because we&rsquo;ve had to do that in the past.</p> <p>The second problem was that I couldn&rsquo;t get the <code>qemu</code> emulation to work at all, so I could never get <code>kpack</code> to work. I didn&rsquo;t want to spend any more time investigating because of other pressing deadlines.</p> <p>So, I backed up to 24.04, the latest LTS release. I still use the Raspberry Pi Imager.</p> <p>Same as before, edit the settings before writing the image:</p> <ul> <li>Configure the hostname</li> <li>Set country</li> <li>Add user (dashaun)</li> <li>Enable SSH server on boot</li> <li>Add public key for SSH auth</li> </ul> <h3 id="update-to-the-latest"> Update to the latest <a class="heading-link" href="#update-to-the-latest"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>After starting up the Raspberry Pi from the USB, I&rsquo;m able to login to it remotely. This time I did have a little monitor attached to the Raspberry Pi so I could see when it was fully booted and at the command prompt.</p> <p>On first login, do all the updates!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update </span></span><span style="display:flex;"><span>sudo apt upgrade -y </span></span><span style="display:flex;"><span>sudo shutdown -r now </span></span></code></pre></div><h2 id="tailscale"> Tailscale <a class="heading-link" href="#tailscale"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p>I&rsquo;m working from multiple networks, even at home I have multiple providers, so I need an easy way to connect to all my nodes. Lately I&rsquo;ve been leaning into <a href="https://tailscale.com/" class="external-link" target="_blank" rel="noopener">tailscale</a>.</p> <p>Right after I do the first reboot, I install <code>tailscale</code>:</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -fsSL https://tailscale.com/install.sh | sh </span></span><span style="display:flex;"><span>sudo tailscale up </span></span></code></pre></div><blockquote> <p>Requires authentication&hellip;</p> </blockquote> <p>After authenticating and adding the device to my VPN, I&rsquo;m feeling pretty good.</p> <h2 id="setup-k3s-using-k3sup"> Setup K3s using K3sup <a class="heading-link" href="#setup-k3s-using-k3sup"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p>I&rsquo;ve got <a href="https://github.com/alexellis/k3sup" class="external-link" target="_blank" rel="noopener">k3sup</a> installed on my laptop. This needs to be ran from another device, not the Raspberry Pi that we are installing on.</p> <p>There are no changes here, I&rsquo;m using the same command I&rsquo;ve used in the past.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>k3sup install --ip 100.81.187.23 --user dashaun --k3s-extra-args <span style="color:#4070a0">&#39;--disable traefik&#39;</span> --merge --local-path ~/.kube/config --context pikorifi </span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># Validate the single-node cluster</span> </span></span><span style="display:flex;"><span>kubectl get nodes -o wide </span></span></code></pre></div><blockquote> <p>I used the VPN ip address of the node so I can use the KUBECONFIG from anywhere on my VPN.</p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME </span></span><span style="display:flex;"><span>pikorifi00 Ready control-plane,master 7m24s v1.33.4+k3s1 10.14.1.18 &lt;none&gt; Ubuntu 24.04.3 LTS 6.8.0-1038-raspi containerd://2.0.5-k3s2 </span></span></code></pre></div><p>At this point, things are very similar to before. But, last time we were using Kubernetes v1.31, this time its v1.33.</p> <h2 id="installing-and-configuring-korifi-on-kubernetes"> Installing and Configuring Korifi on Kubernetes <a class="heading-link" href="#installing-and-configuring-korifi-on-kubernetes"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p><a href="https://github.com/cloudfoundry/korifi/blob/main/INSTALL.md" class="external-link" target="_blank" rel="noopener">The installation instructions that I&rsquo;m following.</a></p> <p>I use <code>direnv</code> and <a href="https://bitwarden.com/" class="external-link" target="_blank" rel="noopener">bitwarden</a> and configured my <code>.envrc</code></p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">BW_SESSION</span><span style="color:#666">=</span><span style="color:#007020;font-weight:bold">$(</span>bw unlock --raw<span style="color:#007020;font-weight:bold">)</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">ROOT_NAMESPACE</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;cf&#34;</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">KORIFI_NAMESPACE</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;korifi&#34;</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">ADMIN_USERNAME</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;cf-admin&#34;</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">BASE_DOMAIN</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;pikorifi00.korifi.cc&#34;</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">GATEWAY_CLASS_NAME</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;contour&#34;</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">DOCKERHUB_USERNAME</span><span style="color:#666">=</span><span style="color:#007020;font-weight:bold">$(</span>bw get username dockerhub-access-token<span style="color:#007020;font-weight:bold">)</span> </span></span><span style="display:flex;"><span><span style="color:#007020">export</span> <span style="color:#bb60d5">DOCKERHUB_PASSWORD</span><span style="color:#666">=</span><span style="color:#007020;font-weight:bold">$(</span>bw get password dockerhub-access-token<span style="color:#007020;font-weight:bold">)</span> </span></span></code></pre></div><h3 id="install-cert-manager"> Install Cert-Manager <a class="heading-link" href="#install-cert-manager"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml </span></span></code></pre></div><h3 id="install-kpack"> Install Kpack <a class="heading-link" href="#install-kpack"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl apply -f https://github.com/buildpacks-community/kpack/releases/download/v0.17.0/release-0.17.0.yaml </span></span></code></pre></div><p>Kpack still doesn&rsquo;t deliver ARM64 images yet. With Kubernetes v1.31 I was able to deploy a <code>daemonset</code> that would install <code>qemu</code> on every node of the cluster. Something has changed. That solution no longer works, and again, I didn&rsquo;t have time to figure out the reason.</p> <p>In order to make that <code>kpack</code> install work, I had to setup <code>qemu</code> on each node manually, but it&rsquo;s super easy.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># On each K3s node</span> </span></span><span style="display:flex;"><span>sudo modprobe binfmt_misc </span></span><span style="display:flex;"><span>sudo apt install -y qemu-user-static </span></span></code></pre></div><blockquote> <p>Almost immediately after finishing that install, the kpack deploy becomes healthy.</p> </blockquote> <p>That was the big change from my last journey down this path. Some of the steps are the same as last time. I&rsquo;m just copying and updating them, so I/we don&rsquo;t have to jump back and forth.</p> <h3 id="install-contour"> Install Contour <a class="heading-link" href="#install-contour"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl apply -f https://raw.githubusercontent.com/projectcontour/contour/release-1.33/examples/render/contour-gateway-provisioner.yaml </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>kubectl apply -f - <span style="color:#4070a0">&lt;&lt;EOF </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">kind: GatewayClass </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">apiVersion: gateway.networking.k8s.io/v1 </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">metadata: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> name: $GATEWAY_CLASS_NAME </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">spec: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> controllerName: projectcontour.io/gateway-controller </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">EOF</span> </span></span></code></pre></div><blockquote> <p>Dynamic provisioning</p> </blockquote> <h3 id="metrics-server"> Metrics Server <a class="heading-link" href="#metrics-server"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>The Kubernetes <code>Metrics Server</code> got installed with <code>k3s</code> so we are moving along, same as last time.</p> <h3 id="install-service-bindings-controller"> Install Service Bindings Controller <a class="heading-link" href="#install-service-bindings-controller"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl apply -f https://github.com/servicebinding/runtime/releases/download/v1.0.0/servicebinding-runtime-v1.0.0.yaml </span></span></code></pre></div><h3 id="create-namespaces"> Create namespaces: <a class="heading-link" href="#create-namespaces"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat <span style="color:#4070a0">&lt;&lt;EOF | kubectl apply -f - </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">apiVersion: v1 </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">kind: Namespace </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">metadata: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> name: $ROOT_NAMESPACE </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> labels: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> pod-security.kubernetes.io/audit: restricted </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> pod-security.kubernetes.io/enforce: restricted </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">EOF</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>cat <span style="color:#4070a0">&lt;&lt;EOF | kubectl apply -f - </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">apiVersion: v1 </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">kind: Namespace </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">metadata: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> name: $KORIFI_NAMESPACE </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> labels: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> pod-security.kubernetes.io/audit: restricted </span></span></span><span style="display:flex;"><span><span style="color:#4070a0"> pod-security.kubernetes.io/enforce: restricted </span></span></span><span style="display:flex;"><span><span style="color:#4070a0">EOF</span> </span></span></code></pre></div><h3 id="add-container-registry-credentials"> Add container registry credentials: <a class="heading-link" href="#add-container-registry-credentials"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl --namespace <span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$ROOT_NAMESPACE</span><span style="color:#4070a0">&#34;</span> create secret docker-registry image-registry-credentials <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --docker-username<span style="color:#666">=</span><span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$DOCKERHUB_USERNAME</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --docker-password<span style="color:#666">=</span><span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$DOCKERHUB_PASSWORD</span><span style="color:#4070a0">&#34;</span> </span></span></code></pre></div><blockquote> <p>I&rsquo;m using DockerHub</p> </blockquote> <h3 id="ssltls"> SSL/TLS <a class="heading-link" href="#ssltls"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>I&rsquo;m still cool with self-signed certificates for now. But with this example, I&rsquo;m going to use <code>cloudflared</code> and talk through making this Raspberry Pi public to the world.</p> <h3 id="install-korifi-with-helm"> Install Korifi with Helm <a class="heading-link" href="#install-korifi-with-helm"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>helm install korifi https://github.com/cloudfoundry/korifi/releases/download/v0.16.0/korifi-0.16.0.tgz <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --namespace<span style="color:#666">=</span><span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$KORIFI_NAMESPACE</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span><span style="color:#bb60d5">generateIngressCertificates</span><span style="color:#666">=</span><span style="color:#007020">true</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span><span style="color:#bb60d5">rootNamespace</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$ROOT_NAMESPACE</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span><span style="color:#bb60d5">adminUserName</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;</span><span style="color:#bb60d5">$ADMIN_USERNAME</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span>api.apiServer.url<span style="color:#666">=</span><span style="color:#4070a0">&#34;api.</span><span style="color:#bb60d5">$BASE_DOMAIN</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span><span style="color:#bb60d5">defaultAppDomainName</span><span style="color:#666">=</span><span style="color:#4070a0">&#34;apps.</span><span style="color:#bb60d5">$BASE_DOMAIN</span><span style="color:#4070a0">&#34;</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span><span style="color:#bb60d5">containerRepositoryPrefix</span><span style="color:#666">=</span>index.docker.io/dashaun/ <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span>kpackImageBuilder.builderRepository<span style="color:#666">=</span>index.docker.io/dashaun/kpack-builder <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --set<span style="color:#666">=</span>networking.gatewayClass<span style="color:#666">=</span><span style="color:#bb60d5">$GATEWAY_CLASS_NAME</span> <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span> --wait </span></span></code></pre></div><blockquote> <p>These are my values, update <code>containerRepositoryPrefix</code> and <code>kpackImageBuilder.builderRepository</code> with your values.</p> </blockquote> <p>Takes a couple of minutes for everything to resolve, but we used <code>--wait</code>, don&rsquo;t panic!</p> <h2 id="post-install-config"> Post-Install Config <a class="heading-link" href="#post-install-config"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <p>Instead of just running this locally, I want to make it public. I&rsquo;m not just making the apps public, I&rsquo;m also making the API public! That will allow my <a href="https://twitch.tv/javagrunt" class="external-link" target="_blank" rel="noopener">Twitch community</a>, to use my Korifi cluster! Stay tuned for more on that later!</p> <h3 id="cloudflared"> Cloudflared <a class="heading-link" href="#cloudflared"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>On my laptop, I have the <a href="https://github.com/cloudflare/cloudflared" class="external-link" target="_blank" rel="noopener">cloudflared</a> CLI installed.</p> <p>The first thing I did was create a tunnel with <code>cloudflared</code>.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cloudflared tunnel create javagrunt </span></span></code></pre></div><blockquote> <p>From my laptop, not on the Raspberry Pi</p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Tunnel credentials written to /Users/dashaun/.cloudflared/89b6e689-2786-44a4-b607-7ee3e3aa6ef8.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel. </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Created tunnel javagrunt with id 89b6e689-2786-44a4-b607-7ee3e3aa6ef8 </span></span></code></pre></div><blockquote> <p>That ID is very important.</p> </blockquote> <p>Then I add that secret to the kubernetes cluster. This isn&rsquo;t the most secure way to handle secrets. I&rsquo;ve been using <a href="https://fluxcd.io/flux/guides/mozilla-sops/" class="external-link" target="_blank" rel="noopener">SOPS</a> for secrets in production.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl create secret generic tunnel-credentials <span style="color:#4070a0;font-weight:bold">\ </span></span></span><span style="display:flex;"><span>--from-file<span style="color:#666">=</span>credentials.json<span style="color:#666">=</span>/Users/dashaun/.cloudflared/89b6e689-2786-44a4-b607-7ee3e3aa6ef8.json </span></span></code></pre></div><blockquote> <p>Points to the file that was generated above</p> </blockquote> <p>With that secret deployed to the cluster, now I can create the tunnel from Cloudflare directly to the Kubernetes cluster on my Raspberry Pi!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#0e84b5;font-weight:bold">---</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">apiVersion</span>:<span style="color:#bbb"> </span>apps/v1<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">kind</span>:<span style="color:#bbb"> </span>Deployment<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">metadata</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">namespace</span>:<span style="color:#bbb"> </span>default<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">spec</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">selector</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">matchLabels</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">app</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">replicas</span>:<span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">template</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">metadata</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">labels</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">app</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">spec</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">containers</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">image</span>:<span style="color:#bbb"> </span>cloudflare/cloudflared:2025.9.0<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">args</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- tunnel<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Points cloudflared to the config file, which configures what</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># cloudflared will actually do. This file is created by a ConfigMap</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># below.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- --config<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- /etc/cloudflared/config/config.yaml<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- run<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">livenessProbe</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">httpGet</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Cloudflared has a /ready endpoint which returns 200 if and only if</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># it has an active connection to the edge.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">path</span>:<span style="color:#bbb"> </span>/ready<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">port</span>:<span style="color:#bbb"> </span><span style="color:#40a070">2000</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">failureThreshold</span>:<span style="color:#bbb"> </span><span style="color:#40a070">1</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">initialDelaySeconds</span>:<span style="color:#bbb"> </span><span style="color:#40a070">10</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">periodSeconds</span>:<span style="color:#bbb"> </span><span style="color:#40a070">10</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">volumeMounts</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>config<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">mountPath</span>:<span style="color:#bbb"> </span>/etc/cloudflared/config<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">readOnly</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Each tunnel has an associated &#34;credentials file&#34; which authorizes machines</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># to run the tunnel. cloudflared will read this file from its local filesystem,</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># and it&#39;ll be stored in a k8s secret.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>creds<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">mountPath</span>:<span style="color:#bbb"> </span>/etc/cloudflared/creds<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">readOnly</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">volumes</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>creds<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">secret</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># By default, the credentials file will be created under ~/.cloudflared/&lt;tunnel ID&gt;.json</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># when you run `cloudflared tunnel create`. You can move it into a secret by using:</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># ```sh</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># kubectl create secret generic tunnel-credentials \</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># --from-file=credentials.json=/Users/yourusername/.cloudflared/&lt;tunnel ID&gt;.json</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># ```</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">secretName</span>:<span style="color:#bbb"> </span>tunnel-credentials<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#60a0b0;font-style:italic"># Create a config.yaml file from the ConfigMap below.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>config<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">configMap</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">items</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">key</span>:<span style="color:#bbb"> </span>config.yaml<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">path</span>:<span style="color:#bbb"> </span>config.yaml<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#0e84b5;font-weight:bold">---</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># This ConfigMap is just a way to define the cloudflared config.yaml file in k8s.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># It&#39;s useful to define it in k8s, rather than as a stand-alone .yaml file, because</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># this lets you use various k8s templating solutions (e.g. Helm charts) to</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#60a0b0;font-style:italic"># parameterize your config, instead of just using string literals.</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">apiVersion</span>:<span style="color:#bbb"> </span>v1<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">kind</span>:<span style="color:#bbb"> </span>ConfigMap<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">metadata</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">name</span>:<span style="color:#bbb"> </span>cloudflared<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">namespace</span>:<span style="color:#bbb"> </span>default<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#062873;font-weight:bold">data</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">config.yaml</span>:<span style="color:#bbb"> </span>|<span style="color:#4070a0;font-style:italic"> </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # Name of the tunnel you want to run </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> tunnel: javagrunt </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> credentials-file: /etc/cloudflared/creds/credentials.json </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # Serves the metrics server under /metrics and the readiness server under /ready </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> metrics: 0.0.0.0:2000 </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # Autoupdates applied in a k8s pod will be lost when the pod is removed or restarted, so </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # autoupdate doesn&#39;t make sense in Kubernetes. However, outside of Kubernetes, we strongly </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # recommend using autoupdate. </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> no-autoupdate: true </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # The `ingress` block tells cloudflared which local service to route incoming </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # requests to. For more about ingress rules, see </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/ingress </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # Remember, these rules route traffic from cloudflared to a local service. To route traffic </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # from the internet to cloudflared, run `cloudflared tunnel route dns &lt;tunnel&gt; &lt;hostname&gt;`. </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # E.g. `cloudflared tunnel route dns example-tunnel tunnel.example.com`. </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> ingress: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # The first rule proxies traffic to the httpbin sample Service defined in app.yaml </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # This rule sends traffic to the built-in hello-world HTTP server. This can help debug connectivity </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # issues. If hello.example.com resolves and tunnel.example.com does not, then the problem is </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> # in the connection from cloudflared to your local service, not from the internet to cloudflared. </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> - hostname: &#34;api.pikorifi00.korifi.cc&#34; </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> service: https://korifi-api-svc.korifi.svc.cluster.local </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> originRequest: </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> noTLSVerify: true </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> - hostname: &#34;*&#34; </span></span></span><span style="display:flex;"><span><span style="color:#4070a0;font-style:italic"> service: http://envoy-korifi.korifi-gateway.svc.cluster.local:80</span><span style="color:#bbb"> </span></span></span></code></pre></div><p>Here are the things you will want to change from that file above.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">image</span>:<span style="color:#bbb"> </span>cloudflare/cloudflared:2025.9.0<span style="color:#bbb"> </span></span></span></code></pre></div><blockquote> <p>Line 19, the latest <code>cloudflared</code> image</p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">tunnel</span>:<span style="color:#bbb"> </span>javagrunt<span style="color:#bbb"> </span></span></span></code></pre></div><blockquote> <p>Line 77, the name of the tunnel</p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">hostname</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;api.pikorifi00.korifi.cc&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">service</span>:<span style="color:#bbb"> </span>https://korifi-api-svc.korifi.svc.cluster.local<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">originRequest</span>:<span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">noTLSVerify</span>:<span style="color:#bbb"> </span><span style="color:#007020;font-weight:bold">true</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span>- <span style="color:#062873;font-weight:bold">hostname</span>:<span style="color:#bbb"> </span><span style="color:#4070a0">&#34;*&#34;</span><span style="color:#bbb"> </span></span></span><span style="display:flex;"><span><span style="color:#bbb"> </span><span style="color:#062873;font-weight:bold">service</span>:<span style="color:#bbb"> </span>http://envoy-korifi.korifi-gateway.svc.cluster.local:80<span style="color:#bbb"> </span></span></span></code></pre></div><blockquote> <p>Lines 97-102, exposes the API, exposes the gateway</p> </blockquote> <p>This is so cool! But we aren&rsquo;t done yet. I need make sure that Cloudflare knows how to handle the route.</p> <p>Inside of Cloudflare, where I&rsquo;m managing DNS, I&rsquo;m adding two CNAME records for the API.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>CNAME | api.pikorifi00.korifi.cc | 89b6e689-2786-44a4-b607-7ee3e3aa6ef8.cfargotunnel.com </span></span><span style="display:flex;"><span>CNAME | *.apps.pikorifi00.korifi.cc | 89b6e689-2786-44a4-b607-7ee3e3aa6ef8.cfargotunnel.com </span></span></code></pre></div><blockquote> <p>The CNAME target is the <!-- raw HTML omitted -->.cfargotunnel.com</p> </blockquote> <p>Now I have a publicly available API for my Korifi foundation!</p> <p>I also added Cloudflare Edge Certificates for <code>*.apps.pikorifi00.korifi.cc</code> and <code>*.pikorifi00.korifi.cc</code>. The $10/month feels like a bargain to enable SSL certs for my Korifi API and all the apps that I deploy.</p> <h2 id="use-that-sweet-sweet-api"> Use that sweet sweet API <a class="heading-link" href="#use-that-sweet-sweet-api"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h2> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf api https://api.<span style="color:#bb60d5">$BASE_DOMAIN</span> </span></span></code></pre></div><blockquote> <p>Exposed with a valid SSL certificate</p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Setting API endpoint to https://api.pikorifi00.korifi.cc... </span></span><span style="display:flex;"><span>OK </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>API endpoint: https://api.pikorifi00.korifi.cc </span></span><span style="display:flex;"><span>API version: 3.117.0+cf-k8s </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Not logged in. Use &#39;cf login&#39; or &#39;cf login --sso&#39; to log in. </span></span></code></pre></div><p>So far, so good!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf login </span></span></code></pre></div><p>I&rsquo;m presented with a list of all of the users in my <code>~/.kube/config</code> and I choose the entry in the list associated to $ADMIN_USERNAME</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>API endpoint: https://api.pikorifi00.korifi.cc </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>1. admin </span></span><span style="display:flex;"><span>2. admin@k3d-korifi </span></span><span style="display:flex;"><span>3. cf-admin </span></span><span style="display:flex;"><span>4. pikorifi </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Choose your Kubernetes authentication info (enter to skip): 3 </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>Authenticating... </span></span><span style="display:flex;"><span>OK </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>API endpoint: https://api.pikorifi00.korifi.cc </span></span><span style="display:flex;"><span>API version: 3.117.0+cf-k8s </span></span><span style="display:flex;"><span>user: cf-admin </span></span><span style="display:flex;"><span>No org or space targeted, use &#39;cf target -o ORG -s SPACE&#39; </span></span></code></pre></div><p>This is working!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf create-org pikorifi-cc </span></span><span style="display:flex;"><span>cf create-space -o pikorifi-cc production </span></span><span style="display:flex;"><span>cf target -o pikorifi-cc -s production </span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>API endpoint: https://api.pikorifi00.korifi.cc </span></span><span style="display:flex;"><span>API version: 3.117.0+cf-k8s </span></span><span style="display:flex;"><span>user: cf-admin </span></span><span style="display:flex;"><span>org: pikorifi-cc </span></span><span style="display:flex;"><span>space: production </span></span></code></pre></div><h3 id="production"> Production <a class="heading-link" href="#production"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf push www --docker-image dashaun/cc.pikorifi.www:latest </span></span></code></pre></div><blockquote> <p>The app is now visible at <a href="https://www.apps.pikorifi00.korifi.cc" class="external-link" target="_blank" rel="noopener">https://www.apps.pikorifi00.korifi.cc</a> with a valid SSL cert</p> </blockquote> <p>That&rsquo;s great, but YOU probably want to use your own domain. Korifi handles that too.</p> <p>If you own <code>pikorifi.cc</code> you simply point your domain to the same tunnel!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>CNAME | pikorifi.cc | 89b6e689-2786-44a4-b607-7ee3e3aa6ef8.cfargotunnel.com </span></span><span style="display:flex;"><span>CNAME | *.pikorifi.cc | 89b6e689-2786-44a4-b607-7ee3e3aa6ef8.cfargotunnel.com </span></span></code></pre></div><blockquote> <p>Pointing to the same tunnel as above</p> </blockquote> <p>Now tell Korifi that we want to handle traffic for that domain! And setup a specific route. Cloudflare provides free certs for the root and *.<!-- raw HTML omitted -->.<!-- raw HTML omitted -->!</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf create-shared-domain pikorifi.cc </span></span><span style="display:flex;"><span>cf create-route pikorifi.cc --hostname www </span></span><span style="display:flex;"><span>cf map-route demo pikorifi.cc --hostname www </span></span></code></pre></div><blockquote> <p>The app is now visible at <a href="https://www.pikorifi.cc" class="external-link" target="_blank" rel="noopener">https://www.pikorifi.cc</a> with a valid SSL cert</p> </blockquote> <p>The API lets us look at the routes.</p> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cf apps </span></span></code></pre></div><blockquote> <p>Check the status of the apps in the target <code>org</code> and <code>space</code></p> </blockquote> <div class="highlight"><pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Getting apps in org pikorifi-cc / space production as cf-admin... </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>name requested state processes routes </span></span><span style="display:flex;"><span>www started web:1/1 www.pikorifi.cc, www.apps.pikorifi00.korifi.cc </span></span></code></pre></div><p>I could remove the default route if I wanted to. I&rsquo;m leaving it around so you can see it for yourself.</p> <p>Check it out for yourself!</p> <p><a href="https://www.pikorifi.cc" class="external-link" target="_blank" rel="noopener">https://www.pikorifi.cc/</a></p> <h3 id="conclusion"> Conclusion <a class="heading-link" href="#conclusion"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p>Korifi keeps getting better. It handles a bunch of stuff for us. I&rsquo;ll be pushing it to its limits in the coming months. Stay tuned! Also, make sure the follow the journey on my <a href="https://twitch.tv/javagrunt" class="external-link" target="_blank" rel="noopener">Twitch channel</a> so you can play along!</p> <p>As always, your feedback is welcomed.</p> <h3 id="links"> Links <a class="heading-link" href="#links"> <i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"></i> <span class="sr-only">Link to heading</span> </a> </h3> <p><a href="https://www.youtube.com/live/3rQpiZLHtpk?si=mTnh80x5ZHbSFjGV" class="external-link" target="_blank" rel="noopener">Multi-Node Korifi on Raspberry Pi from scratch(YouTube)</a> <a href="https://dashaun.com/posts/multi-architecture-spring-oci-from-anywhere-with-paketo/" class="external-link" target="_blank" rel="noopener">Multi-arch Spring Boot OCI images from anywhere with Paketo</a> <a href="https://github.com/pikorifi-cc/cc.pikorifi.www" class="external-link" target="_blank" rel="noopener">https://github.com/pikorifi-cc/cc.pikorifi.www</a> <a href="https://github.com/cloudfoundry/korifi" class="external-link" target="_blank" rel="noopener">Korifi</a> <a href="https://www.youtube.com/live/rcyUKhapsko" class="external-link" target="_blank" rel="noopener">Pragmatic approach to architecture (and development)</a></p> Kennethbcoding on Twitch https://dashaun.com/posts/kennethbcoding/ Tue, 17 Jun 2025 17:37:52 -0500 https://dashaun.com/posts/kennethbcoding/ Refactoring the ROI of Software (Presentation) https://dashaun.com/posts/docs_refactoring-the-roi-of-software-coderemixsummit2025/ Tue, 13 May 2025 14:50:00 -0700 https://dashaun.com/posts/docs_refactoring-the-roi-of-software-coderemixsummit2025/ Testing Your Way to Production Confidence with Native Images (Presentation) https://dashaun.com/posts/docs_testing_native_images/ Sat, 08 Mar 2025 12:00:49 -0600 https://dashaun.com/posts/docs_testing_native_images/