<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>cache cascade</title><link>https://blog.taigrr.com/</link><description>Recent content on cache cascade</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 27 Jun 2025 13:22:25 +0600</lastBuildDate><atom:link href="https://blog.taigrr.com/index.xml" rel="self" type="application/rss+xml"/><item><title>FreeCodeCamp Podcast Announcement</title><link>https://blog.taigrr.com/blog/freecodecamp-podcast/</link><pubDate>Fri, 27 Jun 2025 13:22:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/freecodecamp-podcast/</guid><description>&lt;p&gt;Hello, everyone!
This is not a typical blog post, but I wanted to share my recent guest appearance on the FreeCodeCamp podcast, where I had the pleasure of discussing &lt;a class="grlx"&gt;&lt;/a&gt; grlx, my development journey, some more details on how to choose a programming language, and more.&lt;/p&gt;
&lt;p&gt;We also discussed career advice, the importance of upskilling, and the mindset needed to succeed in the tech industry.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/yDhCweudqyY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;If the embedded video doesn&amp;rsquo;t work, you can watch it directly on YouTube:
&lt;a
href="https://www.youtube.com/watch?v=yDhCweudqyY"
target="_blank"
&gt;FreeCodeCamp Podcast&lt;/a
&gt;.&lt;/p&gt;</description></item><item><title>Why I Chose Go for grlx Instead of Rust</title><link>https://blog.taigrr.com/blog/why-i-chose-golang-for-grlx/</link><pubDate>Wed, 11 Jun 2025 22:31:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/why-i-chose-golang-for-grlx/</guid><description>&lt;h2 id="why-not-rust"&gt;Why Not Rust?&lt;/h2&gt;
&lt;p&gt;There’s been a lot of debate lately about language choices, especially after Microsoft announced that the new TypeScript compiler is being
&lt;a
href="https://devblogs.microsoft.com/typescript/typescript-native-port/"
target="_blank"
&gt;rewritten from TypeScript to Go&lt;/a
&gt;.
This sparked a fresh round of questions on social media: why Go, and why not Rust?
It&amp;rsquo;s a fair question, and one that applies to &lt;a class="grlx"&gt;&lt;/a&gt; grlx as well.
The answer, in both cases, is rooted in pragmatism.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve written extensively in the past on
&lt;a
href="https://blog.taigrr.com/blog/introducing-grlx/#low-overhead"
target="_blank"
&gt;why I chose Go over Python&lt;/a
&gt;, but many of those arguments for Go over Python also apply to Rust over Python!
Between Go and Rust, Go continues to be the language of choice for teams that prioritize development speed, ecosystem maturity, and operational simplicity.
Let&amp;rsquo;s take a look at why.&lt;/p&gt;
&lt;h2 id="familiarity-and-developer-productivity"&gt;Familiarity and Developer Productivity&lt;/h2&gt;
&lt;p&gt;Let’s be honest: I’m a Go developer by trade.
That familiarity translates directly into productivity.
When building &lt;a class="grlx"&gt;&lt;/a&gt; grlx, being able to quickly write, test, and maintain code is more important than chasing theoretical speed or memory guarantees.
My experience with Go allows me to implement features quickly and pivot when needed.
I’ve written production Rust, and I respect it, but Go is where I’m fastest.&lt;/p&gt;
&lt;h2 id="performance-in-practice"&gt;Performance in Practice&lt;/h2&gt;
&lt;p&gt;Rust shines when you need tight control over memory and raw performance.
But, &lt;a class="grlx"&gt;&lt;/a&gt; grlx isn’t compute-bound.
It shells out to system commands, modifies configuration files, and reacts to asynchronous events.
The bottlenecks are I/O and process orchestration, not CPU or memory allocation.
While Rust’s performance profile is technically superior, those benefits wouldn’t be realized in a tool like &lt;a class="grlx"&gt;&lt;/a&gt; grlx.
In practice, Go’s performance is more than sufficient.&lt;/p&gt;
&lt;h2 id="type-systems-and-safety-guarantees"&gt;Type Systems and Safety Guarantees&lt;/h2&gt;
&lt;p&gt;Rust’s type system is more advanced.
That’s a fact.
But for &lt;a class="grlx"&gt;&lt;/a&gt; grlx, it’s not a clear win.
Much of &lt;a class="grlx"&gt;&lt;/a&gt; grlx’s core functionality involves invoking and managing external processes, tasks that don’t gain much from advanced type guarantees.
In many cases, to maintain portability across distributions, the safest way to perform a task is to shell out and handle the output explicitly.
Trying to enforce strict safety via Rust’s type system often leads to awkward wrappers and, ironically, more &lt;code&gt;unsafe&lt;/code&gt; code.
If you’re already bypassing the compiler’s guarantees, the rest of the system doesn’t benefit much from theoretical safety.&lt;/p&gt;
&lt;h2 id="iteration-and-flexibility"&gt;Iteration and Flexibility&lt;/h2&gt;
&lt;p&gt;The &lt;a class="grlx"&gt;&lt;/a&gt; grlx project is still actively evolving.
In this phase, fast iteration is key.
Go’s lightweight syntax and forgiving compiler make it easy to prototype, refactor, and shift architectural decisions as the product matures.
Rust’s compiler enforces rigor early on, which can be helpful in large, stable systems—but it often gets in the way during the creative stage.
Go lets me move faster.&lt;/p&gt;
&lt;h2 id="ecosystem-and-tooling"&gt;Ecosystem and Tooling&lt;/h2&gt;
&lt;p&gt;Go’s ecosystem is mature and focused, especially in the realm of system tooling.
From HTTP servers to CLI libraries, the tools I need already exist and are well-maintained.
For missing pieces, writing custom packages is straightforward.
I’ve built wrappers for systemd, SysV Init, and OpenRC support.
In Go, this feels natural.
Rust’s ecosystem for system-level orchestration is improving but still lags behind in simplicity and breadth.&lt;/p&gt;
&lt;h2 id="readability-and-auditing"&gt;Readability and Auditing&lt;/h2&gt;
&lt;p&gt;The &lt;a class="grlx"&gt;&lt;/a&gt; grlx sprout runs with root permissions, so transparency is critical.
Go’s straightforward syntax and lack of metaprogramming make it easy to understand and audit.
Other developers, ops engineers, and security reviewers can all look at the code and follow what’s going on.
Rust, while expressive, often introduces cognitive overhead via macros, lifetimes, and traits.
This makes security reviews more complex.
With Go, what you see is what you get.&lt;/p&gt;
&lt;h2 id="security-via-simplicity"&gt;Security via Simplicity&lt;/h2&gt;
&lt;p&gt;Using Go’s standard library reduces reliance on external dependencies.
That matters. Every third-party package increases the surface area for supply chain attacks.
With Rust, even basic functionality often comes from external crates, which need to be audited, version-locked, and maintained.
Go includes batteries for most common tasks, making it easier to write secure, auditable, and maintainable code from day one.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Rust is an excellent language, and I wouldn’t hesitate to use it in the right context.
But &lt;a class="grlx"&gt;&lt;/a&gt; grlx isn’t a performance-critical game engine or a systems kernel.
It’s a fleet orchestration tool.
For that purpose, Go’s fast iteration cycle, strong ecosystem, simple deployment model, and security-minded standard library make it the better choice.
This isn’t about hype or language tribalism.
It’s about building the right tool with the right technology—and for &lt;a class="grlx"&gt;&lt;/a&gt; grlx, Go is it.&lt;/p&gt;
&lt;p&gt;If you want to learn more about the tradeoffs between Go and Rust (or TypeScript!), you can
&lt;a
href="https://www.linkedin.com/learning-login/share?forceAccount=false&amp;amp;redirect=https%3A%2F%2Fwww.linkedin.com%2Flearning%2Fchoosing-the-right-back-end-language-typescript-go-or-rust-for-your-greenfield-project%3Ftrk%3Dshare_ent_url%26shareId%3Dv1G%252FaMi3Tu2HoryF4ksy4w%253D%253D"
target="_blank"
&gt;check out my course on LinkedIn Learning.&lt;/a
&gt;
It&amp;rsquo;s free!&lt;/p&gt;
&lt;div style="position:relative;height:0;padding-bottom:56.25%"&gt;&lt;iframe width="640" height="360" src="https://www.linkedin.com/learning/embed/choosing-the-right-back-end-language-typescript-go-or-rust-for-your-greenfield-project/comparing-golang-vs-rust?autoplay=false&amp;claim=AQFAxHGKdxIkJwAAAZdiEHxZA8FZjmZhZc1zT0VNKAGJTYtHmMPJm5olWIqlMvXhR4AUmBrkQKtvhA_D2g0JKs99qJHrAGOgcBHiyKbcaObE7HuRTL3M2PL5ld-A1nVBxRW-6BjfvO3Gze8yPx-uX_0p0Wrmv6ckzL5SOH0KvJZfV7Wn1qna1lyF5Ad6c9oXFA2Lytp8agYzfq8OHNGNK-4uR0ueJchGTjP2N2cWXXbLmS9RqCuTfRoz6UVwOH1sD3Yv9V5UtXmT1mPQahmN7D0slIcZMeeElesXHRiRRoz6BcQIh2fCMEsCWD62EtYpxt-Ul2ukLrdjdLlHu-KYcP9Aa6EdLFNZ5cmCTvGDR0Ovk1nDO7imrBntvYzBgxZjdxAjxfFyfd0W9jy4O0nwd49eGmGeC1uMAw4qUyqr-cm4o-dF0pTxGEbwopvBL0IpcwqyJwrSBOZGvl162D9StJWVUt9i8E2elxm9-S4tbzxqm8vvXy62wTXzwhTNujGWc3DfTRc1kcQzYDzZ3h3PYGPkJg7BdyiSKW83cHK4V54lqQE_ahF7fN_t3l2hiVA09i_EGy6a-QrOwUrwk8RqtqkQyu1CoCE3ALXJT25VMYH1JqbJowcm16a1x0jUhDQfguQ9zo9t9LcFJzvkbnxcflvseXqWDnEReENQYZ9OF7aUKPYfmdi6pYR9O74aLCnHd0Ac6a2nyFvSGrDTBhAyyhvgpxRqQwjUszOczguGGMD9p1QnrkfnoDktDZuSpA4faWRoS8b8Niv_SmuSbEJFVepNnkb9axDua7q_XMlqjamfYuSg56Ivg6XswXdKesObBzYsRp2ptkKw3MjLbXAqu8B7upI3i2DP73HEpszBBZiIjntzs8cGOIXUGo5eMXRPEsjx9Qcm4OqNXPogvsoql6tADt6mCjA5Of3v6yFJJt35gOPVPqvMu2GrOzMzYt27WqY0WBAYrLArolk-VmMdfMIqnVNWambhChPUdRepKkwQD8yYHk2zHYvOtAcSJy-Y1SkeyfgyBsMuFDvPD_N8JjytIWkAqdtJjv56TNBobZOx2DZT83TEE12v9_NLvLjUewG-YZjldY77Bp_PXETs2FkIZRJ5Lw_OHhL4EN7TmFb1pW6sTD-DliFhZ1QrJjJjRO817axND0i8gSg4ELbs1hNVJlQ4rryAeo2FIHeaLjki5dqzWpXTX29Vo0M4MWyQnjC6vfk" mozallowfullscreen="true" webkitallowfullscreen="true" allowfullscreen="true" frameborder="0" style="position:absolute;width:100%;height:100%;left:0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/learning/choosing-the-right-back-end-language-typescript-go-or-rust-for-your-greenfield-project/comparing-golang-vs-rust?trk=embed_lil"&gt;Comparing Golang vs Rust&lt;/a&gt;&lt;/strong&gt; from &lt;strong&gt;&lt;a href="https://www.linkedin.com/learning/choosing-the-right-back-end-language-typescript-go-or-rust-for-your-greenfield-project?trk=embed_lil"&gt;Choosing the Right Back-End Language: TypeScript, Go, or Rust for Your Greenfield Project&lt;/a&gt;&lt;/strong&gt; by &lt;strong&gt;&lt;a href="https://www.linkedin.com/learning/instructors/tai-groot?trk=embed_lil"&gt;Tai Groot&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Using PAM for SSH Login Telephony</title><link>https://blog.taigrr.com/blog/pam-telephony/</link><pubDate>Sun, 18 May 2025 22:31:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/pam-telephony/</guid><description>&lt;h1 id="real-time-ssh-login-alerts-using-pam-and-gotify"&gt;Real-Time SSH Login Alerts Using PAM and Gotify&lt;/h1&gt;
&lt;p&gt;Pluggable Authentication Modules (
&lt;a
href="https://github.com/linux-pam/linux-pam"
target="_blank"
&gt;PAM&lt;/a
&gt;) provide a flexible mechanism for authenticating users on Linux systems.
By integrating PAM with Gotify, you can receive real-time notifications for SSH login attempts, enhancing your system&amp;rsquo;s security and awareness.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this approach for over 5 years across many systems — it&amp;rsquo;s lightweight, reliable, and highly effective.
Let&amp;rsquo;s walk through how to set it up.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you begin, make sure you have the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Linux server with SSH access.&lt;/li&gt;
&lt;li&gt;Root or &lt;code&gt;sudo&lt;/code&gt; privileges.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt; installed.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="step-1-install-and-configure-gotify"&gt;Step 1: Install and Configure Gotify&lt;/h2&gt;
&lt;p&gt;
&lt;a
href="https://gotify.net/"
target="_blank"
&gt;Gotify&lt;/a
&gt; is a simple self-hosted server for sending notifications. You can also use a hosted instance if desired.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Gotify or use an existing instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create an application in Gotify and note down the application token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test that your setup works by sending a test message:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;http://&amp;lt;gotify-server&amp;gt;/message&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-Gotify-Key: &amp;lt;application-token&amp;gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -F &lt;span style="color:#e6db74"&gt;&amp;#34;title=Test Notification&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -F &lt;span style="color:#e6db74"&gt;&amp;#34;message=This is a test message&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="step-2-create-the-pam-notification-script"&gt;Step 2: Create the PAM Notification Script&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll configure PAM to trigger this script every time someone logs in via SSH.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create the script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo vim /usr/local/bin/pam-ssh-notify.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Paste the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GOTIFY_URL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;http://&amp;lt;gotify-server&amp;gt;/message&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GOTIFY_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;lt;gotify-token&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/curl -s -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$GOTIFY_URL&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;X-Gotify-Key: &lt;/span&gt;$GOTIFY_TOKEN&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -F &lt;span style="color:#e6db74"&gt;&amp;#34;title=SSH Login Alert&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&lt;/span&gt; -F &lt;span style="color:#e6db74"&gt;&amp;#34;message=User \&amp;#34;&lt;/span&gt;$PAM_USER&lt;span style="color:#e6db74"&gt;\&amp;#34; logged in from \&amp;#34;&lt;/span&gt;$PAM_RHOST&lt;span style="color:#e6db74"&gt;\&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make it executable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo chmod +x /usr/local/bin/pam-ssh-notify.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="step-3-update-pam-configuration"&gt;Step 3: Update PAM Configuration&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Edit the PAM configuration for SSH:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo vim /etc/pam.d/sshd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add this line at the &lt;strong&gt;end&lt;/strong&gt; of the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;session optional pam_exec.so /usr/local/bin/pam-ssh-notify.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why optional?&lt;/strong&gt;
If Gotify is unreachable and this script fails, PAM will ignore the failure.
If you use &lt;code&gt;required&lt;/code&gt;, login failures could result from simple network errors — not ideal for a monitoring hook.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-4-enable-and-test-it"&gt;Step 4: Enable and Test It&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Ensure the &lt;code&gt;UsePAM&lt;/code&gt; setting is set to &amp;lsquo;yes&amp;rsquo; inside of &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; (or wherever your configuration is).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart SSH:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo systemctl restart sshd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log in from another machine and confirm that your Gotify app receives a notification.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="appendix-pam-environment-variables"&gt;Appendix: PAM Environment Variables&lt;/h2&gt;
&lt;p&gt;When PAM executes your custom script, it passes in useful environment variables that can be used to enhance logic, filtering, and alerting.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_USER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User being authenticated&lt;/td&gt;
&lt;td&gt;Notify only on root logins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_RHOST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remote IP or hostname&lt;/td&gt;
&lt;td&gt;GeoIP filter or blacklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_SERVICE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service name (e.g. &lt;code&gt;sshd&lt;/code&gt;, &lt;code&gt;sudo&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Alert only on &lt;code&gt;sshd&lt;/code&gt; logins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_TTY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminal device (e.g. &lt;code&gt;pts/0&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Log which TTY was used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Event type (&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Differentiate login vs session start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAM_RUSER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Originating user (e.g. for &lt;code&gt;sudo&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Log who used &lt;code&gt;sudo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="considerations"&gt;Considerations&lt;/h2&gt;
&lt;p&gt;While this PAM-to-Gotify setup is simple and effective, it&amp;rsquo;s best suited for single-node systems like personal VPS setups or small self-hosted boxes, systems you&amp;rsquo;re willing to give your full attention to.
It is &lt;strong&gt;not scalable&lt;/strong&gt; for large fleets or production environments with dozens or hundreds of machines.
For those cases, you&amp;rsquo;d want something more robust and asynchronous — like a logging agent with webhook triggers or a centralized security event bus.
For a midrange use case with an audit trail, I&amp;rsquo;ve also swapped out Gotify for a custom Slack App, using the channel webhooks in the past.&lt;/p&gt;
&lt;p&gt;Also, a critical caution: setting your PAM module to &lt;code&gt;required&lt;/code&gt; instead of &lt;code&gt;optional&lt;/code&gt; can be dangerous.
If Gotify (or DNS) is down, a &lt;code&gt;required&lt;/code&gt; script failure will prevent user logins entirely.
This is not hypothetical — in 2021 Facebook famously
&lt;a
href="https://en.wikipedia.org/wiki/2021_Facebook_outage"
target="_blank"
&gt;locked themselves out of their own data center&lt;/a
&gt; because DNS broke, and they couldn&amp;rsquo;t log in to fix the DNS server.
By marking this hook as &lt;code&gt;optional&lt;/code&gt;, you avoid cascading failures. Learn from their mistakes.&lt;/p&gt;
&lt;p&gt;PAM is capable of much more&amp;ndash;adding 2-factor authentication, YubiKey support, LDAP auth, encrypted filesystem mounting, etc.
If you want to learn more,
&lt;a
href="https://wiki.archlinux.org/title/PAM"
target="_blank"
&gt;the ArchLinux Wiki&lt;/a
&gt; is a great source, even if you&amp;rsquo;re not running Arch yourself.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With just a few lines of shell and PAM configuration, you can build powerful real-time login alerts and security automation using Gotify.
The &lt;code&gt;$PAM_*&lt;/code&gt; variables give you rich context for decision-making and logging, and the system is lightweight and reliable.&lt;/p&gt;
&lt;p&gt;Feel free to extend this setup to logins via &lt;code&gt;sudo&lt;/code&gt;, &lt;code&gt;su&lt;/code&gt;, or even failed login attempts — and adapt it to your environment as needed.&lt;/p&gt;
&lt;p&gt;Stay secure!&lt;/p&gt;</description></item><item><title>Temporal Server: Self-hosting a Production-Ready Instance</title><link>https://blog.taigrr.com/blog/setting-up-a-production-ready-temporal-server/</link><pubDate>Tue, 29 Apr 2025 18:19:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/setting-up-a-production-ready-temporal-server/</guid><description>&lt;p&gt;
&lt;a
href="https://temporal.io/"
target="_blank"
&gt;Temporal&lt;/a
&gt; is an open-source workflow orchestration engine designed to manage, execute, and monitor complex workflows and activities.
In plain terms, Temporal allows you to write &lt;strong&gt;crash-proof&lt;/strong&gt; code: it ensures that your long-running processes (workflows) continue execution reliably even if your services (or external services!) crash or restart.&lt;/p&gt;
&lt;p&gt;Temporal&amp;rsquo;s
&lt;a
href="https://temporal.io/setup/start-development-server"
target="_blank"
&gt;walkthroughs&lt;/a
&gt; and CLI provide extremely easy adoption and low-friction for getting started: the temporal CLI contains an embedded development server (&lt;code&gt;temporal server start-dev&lt;/code&gt;), which binds to localhost and allows for easy, unauthenticated workflows.
Connecting to Temporal&amp;rsquo;s managed service is similarly easy, but at writing, has a minimum cost of
&lt;a
href="https://temporal.io/pricing"
target="_blank"
&gt;$100/month&lt;/a
&gt;, which is not always affordable for smaller orgs, especially when running a production-grade self-managed server can cost less than a quarter of that.&lt;/p&gt;
&lt;p&gt;In this article, we’ll set up a &lt;strong&gt;single-node Temporal server&lt;/strong&gt; backed by PostgreSQL, and we’ll secure it with TLS using NGINX as a reverse proxy.
The end result will be Temporal’s services running in Docker containers, accessible at &lt;strong&gt;&lt;code&gt;https://temporal.example.com&lt;/code&gt;&lt;/strong&gt; (our example domain) with encrypted connections.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This setup is suitable for small-scale, development and production deployments. Temporal is designed to scale horizontally and run multiple nodes for high availability and heavy workloads. For production, you should consider a multi-node cluster and follow Temporal’s production best practices (e.g. running each Temporal service separately, managing database schemas manually, etc.) ​
&lt;a
href="https://community.temporal.io/t/local-cluster-external-persistence/5891#:~:text=Here%20is%20the%20docker%20template,server%20role%20in%20own%20container"
target="_blank"
&gt;community.temporal.io&lt;/a
&gt;.
There is significant effort required to deploy and manage multiple horizontal Temporal servers, between certificate rotation and configuration management, especially as Temporal often changes configuration requirements. Once you get to the stage where you need the reliability of multiple Temporal servers, my recommendation is that you
&lt;a
href="https://temporal.io/get-cloud"
target="_blank"
&gt;pay for their managed service&lt;/a
&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="overview-of-the-deployment"&gt;Overview of the Deployment&lt;/h2&gt;
&lt;p&gt;Our deployment will consist of the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Temporal Server&lt;/strong&gt; – runs Temporal’s core services (History, Matching) in one container. We will use the official &lt;code&gt;temporalio/auto-setup&lt;/code&gt; image for convenience, which launches all services in one process and auto-initializes the database schema
&lt;a
href="https://docs.temporal.io/self-hosted-guide/deployment#:~:text=The%20%60temporalio%2Fdocker,for%20producing%20your%20own%20images"
target="_blank"
&gt;docs.temporal.io&lt;/a
&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporal Web UI&lt;/strong&gt; – runs in a separate container (&lt;code&gt;temporalio/ui&lt;/code&gt; image) to provide a web interface for viewing workflows, tasks, and Temporal namespaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; – a single Postgres instance as Temporal’s persistence store (keeping workflow state, history, etc.). I recommend using a managed database for this, like
&lt;a
href="https://www.digitalocean.com/?refcode=aee820706adb&amp;amp;utm_campaign=Referral_Invite&amp;amp;utm_medium=Referral_Program&amp;amp;utm_source=badge"
target="_blank"
&gt;DigitalOcean&lt;/a
&gt; (Disclaimer: this is my personal RefLink to DigitalOcean) to ensure uptime and reliability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NGINX&lt;/strong&gt; – runs on the host as a reverse proxy. It will terminate TLS (HTTPS), forwarding requests to the Temporal WebUI container, and provide Basic Auth support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these services can run on a single machine via Docker Compose (again, I do &lt;em&gt;recommend&lt;/em&gt; using a managed Postgres instance for stability).
The domain &lt;code&gt;temporal.example.com&lt;/code&gt; will point to this machine (use an A or CNAME record!), and we’ll obtain an SSL certificate for it using Let’s Encrypt.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before we begin, make sure you have the following on your VPS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docker and Docker Compose&lt;/strong&gt;: This is how we&amp;rsquo;ll be standing up our temporal services and ensuring they automatically restart&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A domain name&lt;/strong&gt;: for accessing Temporal (e.g., &lt;code&gt;temporal.example.com&lt;/code&gt;), with DNS records pointing to your server’s IP. Don&amp;rsquo;t forget to reserve a public IP! Some cloud platforms will auto-release and reassign a new IP for you if you reboot, and this will break your DNS!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewall Rules&lt;/strong&gt;: Block all incoming traffic to your server, &lt;em&gt;especially&lt;/em&gt; port 8080, and only allow through &lt;code&gt;TCP/7233&lt;/code&gt;, &lt;code&gt;TCP/80&lt;/code&gt;, and &lt;code&gt;TCP/443&lt;/code&gt;. Later, you can whitelist your worker nodes for 7233, or just allow all (&lt;code&gt;0.0.0.0&lt;/code&gt;) to access this port.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certbot&lt;/strong&gt;: Let&amp;rsquo;s Encrypt&amp;rsquo;s CertBot utility can be helpful for provisioning and rotating SSL certs in nginx&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GRPCurl&lt;/strong&gt;: A grpc CLI for doing quick spot-checks on the installation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;temporal (cli)&lt;/strong&gt;: A handy CLI utility for interacting with the Temporal server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re running an Ubuntu/Debian/Debian Derivative, this shell snippet may help:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install docker-compose-v2 nginx python3-certbot-nginx apache2-utils golang
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -L &lt;span style="color:#e6db74"&gt;&amp;#39;https://temporal.download/cli/archive/latest?platform=linux&amp;amp;arch=amd64&amp;#39;&lt;/span&gt; &amp;gt; x.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mv temporal /usr/bin
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm LICENSE x.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, with that out of the way, let’s get started!&lt;/p&gt;
&lt;h3 id="setting-up-our-docker-compose"&gt;Setting Up Our Docker Compose&lt;/h3&gt;
&lt;p&gt;First, create a directory for your Temporal deployment and navigate into it.
Then create a file named &lt;code&gt;docker-compose.yml&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3.5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;temporal&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporalio/auto-setup:${TEMPORAL_VERSION:-1.24.2.1}&lt;/span&gt; &lt;span style="color:#75715e"&gt;# You can substitute for the latest Temporal version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env_file&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;temporal-network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;7233&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;7233&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Expose gRPC frontend port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./certs:/etc/temporal/certs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./base.yaml:/etc/temporal/config/base.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;temporal-ui&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal-ui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporalio/ui:${TEMPORAL_VERSION:-1.24.2.1}&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Use the latest Temporal Web UI version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env_file&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.env.ui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;temporal-network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Expose Web UI port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./ui.yaml:/etc/temporal/config/development.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./certs:/etc/temporal/certs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;temporal-network&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bridge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal-network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, let&amp;rsquo;s set up Postgres.&lt;/p&gt;
&lt;h3 id="configuring-postgres"&gt;Configuring Postgres&lt;/h3&gt;
&lt;p&gt;There are many ways to configure your Postgres instance, and plenty of ways to restrict permissions.
Here are the minimum requirements to make it work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a database named &lt;code&gt;temporal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a database named &lt;code&gt;temporal_visibility&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a user for the Temporal server to use (here we&amp;rsquo;ll assume &lt;code&gt;tuser&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;psql&lt;/code&gt; on your personal/work machine, you&amp;rsquo;ll need to connect to the Postgres instance as an admin and grant full access to the user on these two databases:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Don&amp;rsquo;t forget to whitelist the IP address of your temporal server to access the database! Similarly, you may need to whitelist whichever machine you&amp;rsquo;re using to connect to the database with &lt;code&gt;psql&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CONNECT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DATABASE&lt;/span&gt; temporal &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;USAGE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ALTER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;PRIVILEGES&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;IN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ALL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; TABLES &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Do the same for &lt;code&gt;temporal_visibility&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CONNECT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DATABASE&lt;/span&gt; temporal_visibility &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;USAGE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ALTER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;PRIVILEGES&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;IN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;GRANT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ALL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; TABLES &lt;span style="color:#66d9ef"&gt;TO&lt;/span&gt; tuser;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, let&amp;rsquo;s create some TLS certificates.&lt;/p&gt;
&lt;h3 id="tls-certificates"&gt;TLS certificates&lt;/h3&gt;
&lt;p&gt;Make a directory called certs, and &lt;code&gt;cd&lt;/code&gt; into it.
Inside this directory, we&amp;rsquo;ll generate a RootCA file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl genrsa -out rootCA.key &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -x509 -new -nodes -key rootCA.key -sha256 -days &lt;span style="color:#ae81ff"&gt;3650&lt;/span&gt; -out rootCA.pem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can adjust the expiration time or keysize as you wish.&lt;/p&gt;
&lt;p&gt;Create a client and server key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl genrsa -out client.key &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl genrsa -out server.key &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, using our RootCA, we&amp;rsquo;ll mint some certificates.
Let&amp;rsquo;s start by creating some Certificicate Signing Request (&lt;code&gt;.csr&lt;/code&gt;) files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -key server.key -out server.csr -config server.cnf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll be prompted to answer some questions about your organization.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be sure the commonName and field matches your DNS record, i.e. &lt;code&gt;temporal.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For DNS Alt-names, provide the following:
&lt;ul&gt;
&lt;li&gt;DNS.1: temporal&lt;/li&gt;
&lt;li&gt;DNS.2: temporal.example.com&lt;/li&gt;
&lt;li&gt;IP.1: 127.0.0.1&lt;/li&gt;
&lt;li&gt;IP.2: &lt;code&gt;&amp;lt;YOUR RESERVED IP HERE&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And similarly, for the client:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -key client.key -out client.csr -config client.cnf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, the values don&amp;rsquo;t really matter.
Configure as you see fit.&lt;/p&gt;
&lt;p&gt;Now that we have our &lt;code&gt;.csr&lt;/code&gt;s, let&amp;rsquo;s generate the certificates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -in client.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out client.crt -days &lt;span style="color:#ae81ff"&gt;3650&lt;/span&gt; -sha256 -extfile client.cnf -extensions v3_req
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days &lt;span style="color:#ae81ff"&gt;3650&lt;/span&gt; -sha256 -extfile server.cnf -extensions v3_req
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, you should have a directory that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;temporal@temporal-server:~$ tree
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── certs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── client.cnf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── client.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── client.csr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── client.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── rootCA.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── rootCA.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── rootCA.srl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── server.cnf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── server.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── server.csr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── server.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── docker-compose.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we need to create our &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.ui&lt;/code&gt; files.&lt;/p&gt;
&lt;h3 id="configuring-env-vars"&gt;Configuring Env Vars&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file next to your &lt;code&gt;docker-compose.yml&lt;/code&gt;, replacing the &lt;code&gt;&amp;lt;SUBSTITUTIONS&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POSTGRES_USER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;YOUR USER HERE&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POSTGRES_PWD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;PASSWORD HERE&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DB&lt;span style="color:#f92672"&gt;=&lt;/span&gt;postgres12_pgx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DBNAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;temporal
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VISIBILITY_DBNAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;temporal_visibility
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DB_PORT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;YOUR DB PORT&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POSTGRES_TLS_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POSTGRES_SEEDS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;YOU DB HOSTNAME/IP HERE&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;POSTGRES_TLS_DISABLE_HOST_VERIFICATION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SQL_TLS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SQL_TLS_DISABLE_HOST_VERIFICATION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SQL_TLS_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SQL_HOST_VERIFICATION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SKIP_DB_CREATE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_AUTH_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_ADDRESS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;temporal:7233
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_TLS_REQUIRE_CLIENT_AUTH&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_TLS_CERTS_DIR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/temporal/certs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SKIP_DEFAULT_NAMESPACE_CREATION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DEFAULT_NAMESPACE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;default
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_CLI_TLS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_CLI_TLS_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/temporal/certs/client.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_CLI_TLS_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/temporal/certs/client.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_CLI_TLS_CA_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/temporal/certs/rootCA.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_ADDRESS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;temporal:7233
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, create a &lt;code&gt;.env.ui&lt;/code&gt; file to configure the webui:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_TLS_CA_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;REPLACE WITH BASE64 CERT rootCA.pem&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_TLS_CERT_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;REPLACE WITH BASE64 CERT client.crt&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEMPORAL_TLS_KEY_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;REPLACE WITH BASE64 CERT client.key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can create these base64-encoded values as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo TEMPORAL_TLS_CA_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cat certs/rootCA.pem | base64 -w0&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &amp;gt; .env.ui
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo TEMPORAL_TLS_CERT_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cat certs/client.crt | base64 -w0&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &amp;gt;&amp;gt; .env.ui
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo TEMPORAL_TLS_KEY_DATA&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cat certs/client.key | base64 -w0&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &amp;gt;&amp;gt; .env.ui
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="temporal-configuration"&gt;Temporal Configuration&lt;/h3&gt;
&lt;p&gt;Next to our &lt;code&gt;docker-compose.yml&lt;/code&gt;, you need to create two configuration files, one for &lt;code&gt;temporal-server&lt;/code&gt; and one for the &lt;code&gt;webui&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;base.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tls&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;internode&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;certFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/server.crt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keyFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/server.key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;requireClientAuth&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;clientCaFiles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;client&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;serverName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rootCaFiles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;frontend&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;certFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/server.crt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keyFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/server.key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;requireClientAuth&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;clientCaFiles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rootCaFiles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;client&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;serverName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rootCaFiles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;history&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matching&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;worker&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;log&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;level&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;debug&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And &lt;code&gt;ui.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;tls&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;caFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/rootCA.pem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;certFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/client.crt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keyFile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/temporal/certs/client.key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enableHostVerification&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;serverName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;temporal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="spinning-up"&gt;Spinning Up&lt;/h3&gt;
&lt;p&gt;Next, run &lt;code&gt;docker compose up&lt;/code&gt; and see your services come to life!&lt;/p&gt;
&lt;p&gt;You should see a whole mess of tables populate in your Postgres instance, as the auto-setup image does its thing.&lt;/p&gt;
&lt;p&gt;Once the logs start to slow down and your server starts into a retry loop, go ahead and kill the process with &lt;code&gt;Ctrl+c&lt;/code&gt;.
Wait for the containers to spin down, and we have one change to make to our &lt;code&gt;docker-compose.yml&lt;/code&gt;: swap out the word &lt;code&gt;autosetup&lt;/code&gt; for &lt;code&gt;server&lt;/code&gt; to change our specified container image.
&lt;code&gt;autosetup&lt;/code&gt; is meant to help you easily provision database tables and schema, but
&lt;a
href="https://temporal.io/blog/auto-setup"
target="_blank"
&gt;isn&amp;rsquo;t suitable for production deployments&lt;/a
&gt;, so once our tables are set up, we switch it to the production-ready &lt;code&gt;temporalio/server&lt;/code&gt; image.&lt;/p&gt;
&lt;h3 id="configuring-nginx-as-a-tls-reverse-proxy"&gt;Configuring NGINX as a TLS Reverse Proxy&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re so close to complete! We only have one step left: setting up our &lt;code&gt;nginx&lt;/code&gt; reverse-proxy with password auth.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a config you can drop in on top of &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; (optionally: instead, add a &lt;code&gt;sites&lt;/code&gt; file and link it to &lt;code&gt;sites-enabled&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;user&lt;/span&gt; &lt;span style="color:#e6db74"&gt;www-data&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;worker_processes&lt;/span&gt; &lt;span style="color:#e6db74"&gt;auto&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pid&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/run/nginx.pid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;error_log&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/log/nginx/error.log&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;include&lt;/span&gt; /etc/nginx/modules-enabled/*.conf;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;events&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;worker_connections&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;768&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;http&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sendfile&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tcp_nopush&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;types_hash_max_size&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;include&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/nginx/mime.types&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;default_type&lt;/span&gt; &lt;span style="color:#e6db74"&gt;application/octet-stream&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_protocols&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.1&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.2&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.3&lt;/span&gt;; &lt;span style="color:#75715e"&gt;# Dropping SSLv3, ref: POODLE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;access_log&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/log/nginx/access.log&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gzip&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;upstream&lt;/span&gt; &lt;span style="color:#e6db74"&gt;temporal&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 127.0.0.1:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;auth_basic&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Temporal&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Admins&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Only!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;auth_basic_user_file&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/nginx/.htpasswd&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;temporal.example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://temporal&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_http_version&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Ensuring it can use websockets
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Upgrade&lt;/span&gt; $http_upgrade;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Connection&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-For&lt;/span&gt; $proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_redirect&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://&lt;/span&gt; $scheme://;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Host&lt;/span&gt; $http_host;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;include&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/nginx/conf.d/*.conf&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;include&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/nginx/sites-enabled/*&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add the following to your &lt;code&gt;/etc/hosts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt; EOF &amp;gt;&amp;gt; /etc/hosts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;127.0.0.1 temporal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Generate a &lt;code&gt;.htpasswd&lt;/code&gt; file to control user authentication (you will be prompted to enter a password):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htpasswd -c /etc/nginx/.htpasswd &amp;lt;YOUR USERNAME&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enable and reload &lt;code&gt;nginx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl enable --now nginx &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl reload nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Setup Certbot (this is interactive and may prompt you for your email):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;certbot -d temporal.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Note: don&amp;rsquo;t forget to add &lt;code&gt;certbot renew&lt;/code&gt; to a cron job! LetsEncrypt certs typically expire every 3 months, so check for renewals every 1-2 months.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="testing-the-setup"&gt;Testing the Setup&lt;/h3&gt;
&lt;p&gt;Moment of truth: open your browser and navigate to &lt;strong&gt;&lt;code&gt;https://temporal.example.com&lt;/code&gt;&lt;/strong&gt;.
You should be greeted with a Basic Auth prompt.
Enter your username and password you generated earlier.
Then, you should see Temporal’s Web UI homepage.&lt;/p&gt;
&lt;img
title="image title"
loading="lazy"
decoding="async"
class="img img-fluid img-center"
width="1280"
height="829"
src="https://blog.taigrr.com/images/post/temporal-default-no-workflows_hu_7169e1d5dca610.png"
alt="Default temporal web ui view without any workflows"
onerror="this.onerror='null';this.src='\/images\/post\/temporal-default-no-workflows_hu_7169e1d5dca610.png'" /&gt;
&lt;script&gt;
window.addEventListener("load", (e) =&gt; {
const lightbox = GLightbox();
});
&lt;/script&gt;
&lt;p&gt;If you see something similar to the screenshot above, congratulations! 🎉 You now have a single-node Temporal server running with TLS.
The connection is secure (check that browser lock icon), and you can start building or testing workflows.
The UI will show zero workflows initially (since none have been run); it’s expected.&lt;/p&gt;
&lt;p&gt;If you see errors regarding &amp;ldquo;no default namespace&amp;rdquo;, you can manually create one using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;temporal operator namespace create --namespace default --tls-ca-path certs/rootCA.pem --tls-cert-path certs/client.crt --tls-key-path certs/client.key
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="troubleshooting"&gt;Troubleshooting&lt;/h3&gt;
&lt;h4 id="health-check"&gt;Health Check&lt;/h4&gt;
&lt;p&gt;To further verify that gRPC traffic is working through TLS, you can use &lt;code&gt;grpcurl&lt;/code&gt; to check the health probe:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~/go/bin/grpcurl --cacert certs/rootCA.pem --cert certs/client.crt --key certs/client.key -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;service&amp;#34;: &amp;#34;temporal.api.workflowservice.v1.WorkflowService&amp;#34;}&amp;#39;&lt;/span&gt; temporal:7233 grpc.health.v1.Health/Check
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you see the following, &lt;code&gt;temporal-server&lt;/code&gt; is properly configured:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;SERVING&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="creating-a-test-workflow"&gt;Creating a Test Workflow&lt;/h4&gt;
&lt;p&gt;To verify your task queues are set up, you can create a &lt;code&gt;HELLO WORLD&lt;/code&gt; workflow (note the nested quotes, which are necessary):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;temporal workflow start --address temporal:7233 --type HelloWorldWorkflow --task-queue&lt;span style="color:#f92672"&gt;=&lt;/span&gt;HELLO_WORLD_TASK_QUEUE --tls-ca-path certs/rootCA.pem --tls-cert-path certs/client.crt --tls-key-path certs/client.key --input &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;Hello, World!&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="cleaning-up-tests"&gt;Cleaning up Tests&lt;/h4&gt;
&lt;p&gt;If you&amp;rsquo;ve created too many test workflows, you can Terminate them from within the Web UI or run the following to delete all of them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x in &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;temporal workflow list --address temporal:7233 --tls-ca-path certs/rootCA.pem --tls-cert-path certs/client.crt --tls-key-path certs/client.key | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{ print $2 }&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; temporal workflow delete --address temporal:7233 --tls-ca-path certs/rootCA.pem --tls-cert-path certs/client.crt --tls-key-path certs/client.key -w $x
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="connecting-a-client"&gt;Connecting a Client&lt;/h3&gt;
&lt;p&gt;Because we have self-signed certificates, we need some way to get the certificates into our client.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I do it in go (it will be similar in other languages):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tclient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;crypto/tls&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;crypto/x509&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;encoding/base64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;math/rand&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;sync&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/taigrr/temporal-worker/vars&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;go.temporal.io/sdk/client&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tClient&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientTex&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Mutex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;() (&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientTex&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;clientTex&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tClient&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tClient&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// base64-encoded TLS certs created the same way we did for .env.ui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientCertData&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TemporalTLSCertData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientCertKeyData&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TemporalTLSKeyData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;serverRootCAData&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TemporalTLSCAData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// the FQDN:PORT i.e. temporal.example.com:7233&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;hostPort&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TemporalHostPort&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TemporalNamespace&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Decode the base64 encoded keys and certs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientCert&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StdEncoding&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DecodeString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientCertData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;unable to decode client cert data: %w&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientCertKey&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StdEncoding&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DecodeString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientCertKeyData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;unable to decode client cert key data: %w&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;serverRootCA&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StdEncoding&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DecodeString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;serverRootCAData&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;unable to decode server root CA data: %w&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cert&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tls&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;X509KeyPair&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientCert&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;clientCertKey&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;unable to load cert and key pair: %w&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Create Certpool to add the server root CA to the client pool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;certPool&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;x509&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewCertPool&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;certPool&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AppendCertsFromPEM&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;serverRootCA&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Ensure no two workers have the same ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;randString&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;%04x&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;rand&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Intn&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0x10000&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Embed the git commit of the currently running code into the worker ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;commitHash&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WorkerCommit&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; len(&lt;span style="color:#a6e22e"&gt;commitHash&lt;/span&gt;) &amp;gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;commitHash&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;commitHash&lt;/span&gt;[:&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;workerID&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;%s-%s-%s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WorkerID&lt;/span&gt;), &lt;span style="color:#a6e22e"&gt;commitHash&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;randString&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Add the cert to the tls certificates in the ConnectionOptions of the Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientOptions&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Options&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;HostPort&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;hostPort&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Namespace&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ConnectionOptions&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ConnectionOptions&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLS&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;tls&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Config&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Certificates&lt;/span&gt;: []&lt;span style="color:#a6e22e"&gt;tls&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Certificate&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;cert&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;RootCAs&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;certPool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Identity&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;workerID&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;temporalClient&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Dial&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientOptions&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;unable to connect to Temporal: %w&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tClient&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;temporalClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tClient&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;workerID&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="wrap-up"&gt;Wrap Up&lt;/h2&gt;
&lt;p&gt;We’ve successfully deployed Temporal on a single node with Docker Compose and added TLS termination with NGINX.&lt;/p&gt;
&lt;p&gt;In summary, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introduced Temporal and set up a &lt;strong&gt;PostgreSQL-backed Temporal server&lt;/strong&gt; using Docker Compose&lt;/li&gt;
&lt;li&gt;Used Temporal’s auto-setup image to provision our databases&lt;/li&gt;
&lt;li&gt;Switched to the Temporal production server&lt;/li&gt;
&lt;li&gt;Deployed the &lt;strong&gt;Temporal Web UI&lt;/strong&gt; and passed it through NGINX via a friendly HTTPS URL&lt;/li&gt;
&lt;li&gt;Configured &lt;strong&gt;NGINX&lt;/strong&gt; to handle TLS, forwarding web requests to the UI, thereby securing the traffic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup provides a starting point for working with Temporal’s powerful workflow capabilities.
You can now write workflows in your language of choice (Go, Java, Python, etc.), point the SDK client to &lt;code&gt;temporal.example.com:7233&lt;/code&gt;, and begin executing workflows on your server&amp;ndash;just make sure your self-signed certificates are imported (see my go example above)!&lt;/p&gt;
&lt;p&gt;Before we conclude, a few final notes and best practices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; Your NGINX config can also restrict access by IP. If you intend to expose the Temporal UI or gRPC port more broadly, consider additional security layers, i.e. cloud infra-level firewalls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistence:&lt;/strong&gt; Your temporal workflows are only as stable as your database. Again, I highly recommend using a stable, managed solution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certificates&lt;/strong&gt;: A time will come when your certificates expire. Be sure to have a rotation plan in place for scheduled maintenance!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Postgres&lt;/strong&gt;: We initially granted sweeping access to the &lt;code&gt;tuser&lt;/code&gt; in Postgres for our &lt;code&gt;temporal&lt;/code&gt; and &lt;code&gt;temporal_visibility&lt;/code&gt; databases. Now that the schema is set up, consider locking that down.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Temporal brings reliability superpowers to your applications – even in this modest single-node setup, you can develop and test workflows that will &lt;strong&gt;never give up&lt;/strong&gt; in the face of failures.
Now that you have Temporal running at &lt;code&gt;temporal.example.com&lt;/code&gt;, it’s time to start orchestrating some workflows.
Have fun exploring Temporal, and may your workflows always complete (eventually)! 🚀&lt;/p&gt;
&lt;img
title="image title"
loading="lazy"
decoding="async"
class="img img-fluid img-center"
width="1220"
height="829"
src="https://blog.taigrr.com/images/post/temporal-many-workflows_hu_dd332e20a2709111.png"
alt="Default temporal web ui view without any workflows"
onerror="this.onerror='null';this.src='\/images\/post\/temporal-many-workflows_hu_dd332e20a2709111.png'" /&gt;</description></item><item><title>Introducing grlx</title><link>https://blog.taigrr.com/blog/introducing-grlx/</link><pubDate>Tue, 07 Nov 2023 19:17:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/introducing-grlx/</guid><description>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;In a
&lt;a
href="https://blog.taigrr.com/blog/so-long-salt-project/"
target="_blank"
&gt;recent blog post&lt;/a
&gt;, I discussed a few of the reasons why in 2021, I began working on a new Fleet Configuration Management tool to replace SaltStack.
However, I did not go into much detail about what the replacement is, how it works, or the roadmap into the future.&lt;/p&gt;
&lt;p&gt;As mentioned in the prior post, the first inspiration to build a new tool arose from requirements regarding constrained memory environments.
In our typical install, salt-minion at idle required between 140mb and 300mb of memory.
(This wide variance in metrics results from several factors including the method of measurement, the use case, the system uptime, the service uptime, and the versions of salt-minion and python being run.
Regardless, this is simply too much memory for salt to be usable in many situations.)&lt;/p&gt;
&lt;p&gt;For widely-deployed, 1GB IoT systems, sacrificing between 10-30% of all available memory for a background task is not ideal.
But in a world where even
&lt;a
href="https://www.gsmarena.com/google_pixel_watch_2-12547.php"
target="_blank"
&gt;wrist watches ship with 2GB of RAM&lt;/a
&gt;, and IoT components are getting cheaper by the month, maybe 300mb is not that big of a deal?&lt;/p&gt;
&lt;p&gt;For the virtual machine use case, I’d argue it’s still very important.
Linode (now Akamai) offers VPS systems starting at 1GB/mo.
Amazon Lightsail’s smallest offering comes with only half a gigabyte of memory.
Imagine trying to scale horizontally across many nodes only for half of the memory you paid for to be eaten up by a system management tool running in the background.&lt;/p&gt;
&lt;p&gt;Without repeating too much from the prior blogpost, I challenge the reader to pause for a moment and consider the hurdles with their own configuration management software, memory requirements aside: how easy was it to get started?
What kind of maintenance is required to keep things running and secured?
Does it break?
How often?
What is the DX (Developer eXperience) like?
Is the current solution easy (and safe!) to use at 2am for an emergency rollback from your parents’ house on holiday?&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;When I set out to build this tool from scratch, I saw it as a golden opportunity to not only solve existing problems but also to shape the tool according to my vision.
The goal was clear: to develop a tool that functions seamlessly, aligning to the needs of the landscape, including the creature comforts missing from other options.
It should be low overhead, offer familiarity with existing tools, be secure by default, easily scalable, and resilient to OS upgrades (read: runtime dependency-free).&lt;/p&gt;
&lt;h2 id="introducing-grlx---effective-fleet-configuration-management"&gt;Introducing grlx - Effective Fleet Configuration Management&lt;/h2&gt;
&lt;p&gt;Without any further introduction, here are some of the major issues we are looking to address in &lt;a class="grlx"&gt;&lt;/a&gt; grlx compared to other tools:&lt;/p&gt;
&lt;h4 id="low-overhead"&gt;Low Overhead&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ve mentioned this one a lot already, but it bears repeating.
The primary reason that drove us to pursue building &lt;a class="grlx"&gt;&lt;/a&gt; grlx was the unavoidable memory footprint of the interpreted language ecosystems.
While our first choice was to simple take an existing tool and fork it, paring down the featureset and optimizing memory, we quickly realized this would be an exercise in futility&amp;ndash;we might see slight improvement, but nothing on the order of magnitude we were hoping for.
The CPU usage of &lt;a class="grlx"&gt;&lt;/a&gt; grlx should also be lower for the same reason: no JIT compilation or JVM (looking at you, Closure) taking up intermediate cycles.&lt;/p&gt;
&lt;h4 id="dependency-free-by-default"&gt;Dependency-free by default&lt;/h4&gt;
&lt;p&gt;A tool written in Python or Ruby depends on the environment&amp;rsquo;s Python and Ruby packages for its own stability.
When a primary goal of a system and configuration management tool is to keep the system up to date, and updating the system involves updating system libraries such as Python or Ruby libraries, there is always a small but non-zero risk that a breakage in the ecosystem APIs will crash the management tool and prevent it from restarting, leaving the node (or worse, the whole fleet) stranded and unresponsive.
&lt;a
href="https://docs.python.org/3/whatsnew/3.7.html"
target="_blank"
&gt;Python 3.7&lt;/a
&gt; for example, introduced new reserved words to the language (&lt;code&gt;await&lt;/code&gt; and &lt;code&gt;async&lt;/code&gt;) which subsequently broke many packages that hadn&amp;rsquo;t been updated in time.
Typically, these ecosystems move slowly enough for maintainers to update in time, but it&amp;rsquo;s never a good idea to be stuck between waiting for a new software release and keeping your ecosystem out-of-date and vulnerable to prevent breakages.&lt;/p&gt;
&lt;h4 id="scalable-and-fault-tolerant"&gt;Scalable and Fault-tolerant&lt;/h4&gt;
&lt;p&gt;Built using NATS.io, &lt;a class="grlx"&gt;&lt;/a&gt; grlx can take advantage of NATS&amp;rsquo;s clustering and fault-tolerance features.
Using only a single farmer system, you can join several more NATS Server instances to the cluster and have them all share the load of incoming messages.
Read more about NATS clustering
&lt;a
href="https://docs.nats.io/nats-server/configuration/clustering"
target="_blank"
&gt;here&lt;/a
&gt;.&lt;/p&gt;
&lt;p&gt;Future additions to &lt;a class="grlx"&gt;&lt;/a&gt; grlx will allow for a single sprout to connect to multiple independent farmers, to allow for failover across control servers.&lt;/p&gt;
&lt;h4 id="secure-by-default"&gt;Secure by default&lt;/h4&gt;
&lt;p&gt;Did you know that it&amp;rsquo;s a violation of
&lt;a
href="https://docs.saltproject.io/en/latest/topics/hardening.html"
target="_blank"
&gt;SaltStack best practices&lt;/a
&gt; (specifically the linked Hardening Rules page) to use Salt over the internet without taking precautionary encryption measures?
It commonly recommended to use a tool like
&lt;a
href="https://www.stunnel.org/"
target="_blank"
&gt;stunnel&lt;/a
&gt; or
&lt;a
href="https://blog.taigrr.com/blog/provisioning-wireguard/"
target="_blank"
&gt;WireGuard&lt;/a
&gt;® to add an extra layer between minions and the master.&lt;/p&gt;
&lt;p&gt;With &lt;a class="grlx"&gt;&lt;/a&gt; grlx, there&amp;rsquo;s no need for a third-party encryption suite to stay secure.
All communications from cli to farmer, and from farmer to sprout, are encrypted using self-signed
&lt;a
href="https://github.com/gogrlx/grlx/blob/master/certs/tls.go"
target="_blank"
&gt;TLS certificates&lt;/a
&gt; and
&lt;a
href="https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/nkey_auth"
target="_blank"
&gt;NATS.io NKey encryption&lt;/a
&gt;.
These certificates are pinned to the clients on first connection as an extra security precaution.&lt;/p&gt;
&lt;h4 id="easy-to-set-up"&gt;Easy to set up&lt;/h4&gt;
&lt;p&gt;Easy to automate.
Need it be said again?
Only one binary (and one configuration file) is required to provision a sprout (node).&lt;/p&gt;
&lt;p&gt;We also offer a
&lt;a
href="https://docs.grlx.dev/docs/bootstrapping"
target="_blank"
&gt;bootstrap script&lt;/a
&gt; hosted at
&lt;a
href="https://bootstrap.grlx.dev"
target="_blank"
&gt;bootstrap.grlx.dev&lt;/a
&gt; that will always get you the latest and greatest release in a simple one-liner, if you so choose.&lt;/p&gt;
&lt;h4 id="easy-to-understand"&gt;Easy to Understand&lt;/h4&gt;
&lt;p&gt;Go is often touted as simple language. All &lt;a class="grlx"&gt;&lt;/a&gt; grlx code is written in Go (except for the Web UI), and we explain how both the overall system and the individual components work in the
&lt;a
href="https://docs.grlx.dev"
target="_blank"
&gt;documentation&lt;/a
&gt;.
Auditing the &lt;a class="grlx"&gt;&lt;/a&gt; grlx codebase is easy, and most of the source code can be read through in a day or so.&lt;/p&gt;
&lt;p&gt;Our non-stdlib build dependencies are selective&amp;ndash;kept to as short a list as reasonably possible, so there&amp;rsquo;s little happening &amp;ldquo;elsewhere.&amp;rdquo;
We follow best practices, strive for high test coverage, and keep an eye on our
&lt;a
href="https://goreportcard.com/report/github.com/gogrlx/grlx"
target="_blank"
&gt;Go report card&lt;/a
&gt;.&lt;/p&gt;
&lt;h4 id="open-source"&gt;Open Source&lt;/h4&gt;
&lt;p&gt;It would be crazy to install a tool on a fleet of systems and give it root access without the ability to do some introspection on what&amp;rsquo;s happening under the hood.
All the source is available
&lt;a
href="https://github.com/gogrlx/grlx"
target="_blank"
&gt;on GitHub&lt;/a
&gt;, where I encourage you to give it a star!&lt;/p&gt;
&lt;p&gt;Not only is &lt;a class="grlx"&gt;&lt;/a&gt; grlx open and free, it&amp;rsquo;s also permissively licensed as
&lt;a
href="https://opensource.org/license/0bsd/"
target="_blank"
&gt;0BSD&lt;/a
&gt; and is therefore compatible with nearly any enterprise company&amp;rsquo;s policy on OSS software.&lt;/p&gt;
&lt;p&gt;Keep in mind, the logos, the mascot (also referred to as &amp;ldquo;Clove&amp;rdquo;), the name &amp;ldquo;grlx&amp;rdquo; itself, and the overall brand are copyrighted, all rights reserved, etc.
Do not try to pass off &lt;a class="grlx"&gt;&lt;/a&gt; grlx as your own&amp;ndash;but forking and renaming is allowed!&lt;/p&gt;
&lt;h4 id="supported-with-corporate-backing-and-buy-in"&gt;Supported with Corporate Backing and Buy-in&lt;/h4&gt;
&lt;p&gt;&lt;a class="grlx"&gt;&lt;/a&gt; grlx has entered into an agreement with
&lt;a
href="https://www.adatomic.com"
target="_blank"
&gt;ADAtomic, Inc.&lt;/a
&gt;, to offer official, commercial support.
If this is something that would be valuable to your company or organization,
&lt;a
href="mailto:grlx@adatomic.com?subject=Commercial%20Support%20Inquiry"
&gt;please contact ADAtomic here&lt;/a
&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, &lt;a class="grlx"&gt;&lt;/a&gt; grlx is already seeing production use from several companies,
&lt;a
href="https://github.com/gogrlx/grlx"
target="_blank"
&gt;listed in the README&lt;/a
&gt;.
As mentioned above, &lt;a class="grlx"&gt;&lt;/a&gt; grlx development is being driven by a real need at our organization, and is seeing heavy usage in our own lineup of products.&lt;/p&gt;
&lt;h4 id="extensible-plugin-system"&gt;Extensible Plugin System&lt;/h4&gt;
&lt;p&gt;To state the obvious, not every possible feature is required by every customer.
To keep &lt;a class="grlx"&gt;&lt;/a&gt; grlx light and fast, all endpoints and features take advantage of dependency injection and there is a well-defined interface already in place for loading plugins at runtime.
These plugins can offer features such as supporting obscure package managers or downloading files from uncommon endpoints.
In short order, two example file endpoint provider plugins will be released, one to download files from IPFS, and another to download from a BitTorrent magnet link.
Hopefully, these examples indicate both the reason for and capability of the plugin system.
Using runtime-loaded plugins is also a boon for development and testing, as mainline code that should be added to &lt;a class="grlx"&gt;&lt;/a&gt; grlx can start as a plugin for development, and get merged over time.&lt;/p&gt;
&lt;p&gt;At first, the plugin interface will only support Go plugins, but support for WASM plugins is on the roadmap, allowing for plugin development in nearly any compiled language.&lt;/p&gt;
&lt;p&gt;To be clear, &lt;a class="grlx"&gt;&lt;/a&gt; grlx &lt;em&gt;is dependency-free by default&lt;/em&gt;, but we recognize there are some use cases where optional plugins may offer increased functionality without adding significant hooks into environmental state.
Both Go and WASM plugins can be distributed as single files, dropped into a directory and hot-reloaded automatically at runtime.&lt;/p&gt;
&lt;h4 id="configurable-message-delivery-rules"&gt;Configurable Message Delivery Rules&lt;/h4&gt;
&lt;p&gt;Some tools only allow for deploys to go out the door to nodes that are online at the time of the push&amp;ndash;if it&amp;rsquo;s not online &lt;em&gt;right now&lt;/em&gt; then you have to either watch and wait for it to come online, or write some scripts to programmatically check for the next time it does come online and push the release then.
In a datacenter, this is not a big deal as you should have extremely high (near 100%) uptime and connectivity between the CNC Server and the nodes.
For an IoT deployment on the other hand, connectivity can be tricky, and the aforementioned scripts must come into use.&lt;/p&gt;
&lt;p&gt;However, once you start writing them, you might realize that there&amp;rsquo;s a lot more involved regarding error checking, reporting, logging, etc., that the simplest &lt;code&gt;while ! online; do sleep 60; done &amp;amp;&amp;amp; push&lt;/code&gt; script cannot handle.
You end up writing a whole deployment framework around trying to catch your node when it&amp;rsquo;s online and push to it at that exact moment, or maybe you add a cron job to the node itself to trigger an agentless or standalone mode periodically.&lt;/p&gt;
&lt;p&gt;This whole mess could have been avoided if the configuration management tool provided a utility for you, which stores the deploy jobs into persistent queues to ensure delivery on next reconnection.
NATS.io&amp;rsquo;s concept of
&lt;a
href="https://docs.nats.io/nats-concepts/jetstream/consumers"
target="_blank"
&gt;Durable Queues&lt;/a
&gt; allows for exactly this behavior.&lt;/p&gt;
&lt;h4 id="rbac"&gt;RBAC&lt;/h4&gt;
&lt;p&gt;&lt;a class="grlx"&gt;&lt;/a&gt; grlx takes a Role-Based Access Control (RBAC) approach for designating who is allowed to deploy what&amp;ndash;and where.
Secured service worker accounts or admins might be allowed root access to all machines.
Your coworker John, who manages some &lt;code&gt;nginx&lt;/code&gt; configurations, can &lt;code&gt;cook&lt;/code&gt; (push out) the &lt;code&gt;nginx&lt;/code&gt; recipe to all sprouts, and Celine has access to &lt;code&gt;cook&lt;/code&gt; all recipes but only on select development/canary nodes.
Future work will allow these roles to come from an LDAP endpoint or other external service provider plugin.&lt;/p&gt;
&lt;p&gt;More information about the RBAC system can be found in the
&lt;a
href="https://docs.grlx.dev/docs/rbac"
target="_blank"
&gt;documentation&lt;/a
&gt;.&lt;/p&gt;
&lt;h3 id="usability-improvement-features"&gt;Usability Improvement Features&lt;/h3&gt;
&lt;p&gt;Below, find some of the small tweaks and differences to make using grlx easier than competing tools:&lt;/p&gt;
&lt;h4 id="advanced-developer-tooling"&gt;Advanced Developer Tooling&lt;/h4&gt;
&lt;p&gt;Our development team is hard at work creating an LSP server and Linter for &lt;a class="grlx"&gt;&lt;/a&gt; grlx.
The roadmap includes support for jumping to import definitions, full syntax highlighting (even in recipe files containing templates!), and property checking.
Our plugin interface also includes definitions to generate custom LSP inputs so that the tools you might need to build in-house are treated as first-class citizens in your editor.&lt;/p&gt;
&lt;h4 id="official-gui-and-tui-built-in"&gt;Official GUI and TUI built-in&lt;/h4&gt;
&lt;p&gt;Many home-grown WebUIs have been built to support existing solutions which don&amp;rsquo;t come with official WebUIs.
The &lt;a class="grlx"&gt;&lt;/a&gt; grlx command line utility will soon support spinning up a local webUI, server over localhost, which can be used to access most functionality.
Communications to the farmer are proxied through the CLI, so there&amp;rsquo;s no need to open up ports on the farmer to access the WebUI.&lt;/p&gt;
&lt;p&gt;A TUI (Terminal UI), built with
&lt;a
href="https://github.com/charmbracelet/bubbletea"
target="_blank"
&gt;BubbleTea&lt;/a
&gt; is on the roadmap.&lt;/p&gt;
&lt;h4 id="optional-dropshell-support"&gt;Optional DropShell Support&lt;/h4&gt;
&lt;p&gt;While &lt;a class="grlx"&gt;&lt;/a&gt; grlx already supports arbitrary command execution, sometimes it happens where you really just need a shell to do some in-depth troubleshooting.
For SaltStack, I developed TunnelBunny, a process for setting up port forwarding and reverse shells to minions, which you can read about
&lt;a
href="https://blog.taigrr.com/blog/tunnelbunny/"
target="_blank"
&gt;here&lt;/a
&gt;.
&lt;a class="grlx"&gt;&lt;/a&gt; grlx will support a similar feature, called DropShell, which will allow you to drop into a shell on a single sprout, from your local machine, without having to open up any ports on the minion, configure SSH, or drop firewall rules.
This feature is still in development, but will be available once RBAC is fully implemented, as it might be a security concern for some organizations.&lt;/p&gt;
&lt;h4 id="high-test-coverage"&gt;High test coverage&lt;/h4&gt;
&lt;p&gt;We strive for 100% test coverage on that parts that matter.
Specifically, all ingredients are thoroughly unit tested, and the core functionality of the platform runs through a suite of integration tests inside of a docker-compose environment.&lt;/p&gt;
&lt;h4 id="no-server-access-required"&gt;No Server Access Required&lt;/h4&gt;
&lt;p&gt;The command line utility is separate from the farmer, and can be run from any machine with network access to the farmer.
All communication is encrypted using TLS and NKey encryption, so there&amp;rsquo;s no need to provide a VPN or SSH tunnel to the farmer.
Depending on company policy or developer preference, the CLI can be run directly on developer machines, or from a dedicated bastion server.
Authentication and authorization is handled on a per-user basis (to support RBAC), so there&amp;rsquo;s no need to share credentials, SSH keys, or configure &lt;code&gt;sudo&lt;/code&gt; access to the main CNC server.&lt;/p&gt;
&lt;h4 id="supports-gitops"&gt;Supports GitOps&lt;/h4&gt;
&lt;p&gt;The command line utility &lt;code&gt;cook&lt;/code&gt; command will soon support a &lt;code&gt;--git&lt;/code&gt; flag, which will tell the &lt;code&gt;farmer&lt;/code&gt; to check out a particular branch, tag, or commit of a git repository, and use the files in that branch as the source of truth for the recipes.
Additionally, the currently checked-out commit, tag, and branch are made available to the recipe files as property variables, so you can use them in your templates should the need arise.
There is also a command to update the recipe&amp;rsquo;s git repository to the latest commit, tag, or branch, and a command to list all available branches, tags, and commits.&lt;/p&gt;
&lt;h4 id="synchronous-job-status-reporting"&gt;Synchronous Job Status reporting&lt;/h4&gt;
&lt;p&gt;One of the most frustrating things about using a configuration management tool is the lack of visibility into what&amp;rsquo;s happening, as it&amp;rsquo;s happening.
When submitting a job, you might get a job ID back for polling, or wait synchronously for the job to finish and see the summary of results but most tools won&amp;rsquo;t let you watch the states finish as they happen.
Most of the time, this is fine, but the few times you need live introspection into why a deploy is stuck, you&amp;rsquo;re out of luck.&lt;/p&gt;
&lt;p&gt;&lt;a class="grlx"&gt;&lt;/a&gt; grlx will soon support a &lt;code&gt;--watch&lt;/code&gt; flag on the &lt;code&gt;cook&lt;/code&gt; command, which will stream the job status to the terminal as it happens, with a live progress bar and summary of results at the end.&lt;/p&gt;
&lt;h4 id="restful-api"&gt;Restful API&lt;/h4&gt;
&lt;p&gt;The farmer exposes a RESTful API for all of its functionality, so you can build your own tools around it.
If you don&amp;rsquo;t like the built-in WebUI, you have the option to build your own!
If you need to build automations around your deployment from a third-party tool, you can do that too!
Everything from &lt;code&gt;cook&lt;/code&gt;ing to command execution is available via the API.&lt;/p&gt;
&lt;p&gt;More information about the API can be found in the
&lt;a
href="https://docs.grlx.dev/docs/api"
target="_blank"
&gt;documentation&lt;/a
&gt;.&lt;/p&gt;
&lt;h4 id="lifecycle-hooks-for-outgoing-webhooks"&gt;Lifecycle Hooks for Outgoing Webhooks&lt;/h4&gt;
&lt;p&gt;Do you need to track one particular step of a deploy for a particular reason?
Maybe you need to know whenever a specific file is created or modified?
Recipe files have a field for providing an upstream webhook URL and JSON body you need sent.
Hooks can also be configured to fire off WebHooks when a deploy starts, fails, or finishes.
Additionally, you can configure a hook to fire when a new sprout is added to the fleet, or when a sprout&amp;rsquo;s online status has changed.&lt;/p&gt;
&lt;p&gt;Introspection and visibility has never been this easy!&lt;/p&gt;
&lt;h4 id="friendly-discord"&gt;Friendly Discord&lt;/h4&gt;
&lt;p&gt;Our Discord invite is open to one and all, where you can chat with the developers of &lt;a class="grlx"&gt;&lt;/a&gt; grlx and get involved in the &lt;a class="grlx"&gt;&lt;/a&gt; grlx community.&lt;/p&gt;
&lt;p&gt;Meet like-minded DevOps professionals and swap ideas about the best way to get something deployed&amp;ndash;or embroil yourself into a flame war over Cloud or On-Prem&amp;ndash;as long as you keep things respectful it&amp;rsquo;s up to you!&lt;/p&gt;
&lt;h4 id="cute-mascot"&gt;Cute Mascot!&lt;/h4&gt;
&lt;center&gt;
&lt;img src="https://blog.taigrr.com/images/post/grlx.jpg" width="200" &gt;
&lt;br&gt;
His name is Clove, isn’t he great?
&lt;/center&gt;
&lt;h3 id="summary"&gt;Summary&lt;/h3&gt;
&lt;p&gt;In summary, I hope this article has presented the litany of reasons for a new tool in the configuration and system management space.
Our roadmap is clear, and we are excited to announce that version 1.0.x is available for download now.
Please see
&lt;a
href="https://www.adatomic.com"
target="_blank"
&gt;ADAtomic.com&lt;/a
&gt; for more information!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Updating or rolling back production from your parents’ house is not a recommended DevSecOps or GitOps practice, nor am I endorsing it in any way. Ideally, you’ve got canaries and auto-rollbacks, split traffic, staging environments&amp;hellip;
This statement is meant to be humorous, don’t take it literally.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>So Long, Salt Project</title><link>https://blog.taigrr.com/blog/so-long-salt-project/</link><pubDate>Mon, 09 Oct 2023 18:19:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/so-long-salt-project/</guid><description>&lt;p&gt;This post has been a long time in the making.
Anyone who has followed me and my work for any period of time probably knows by know that I&amp;rsquo;ve been working on &lt;a class="grlx"&gt;&lt;/a&gt;
&lt;a
href="https://grlx.dev/grlx"
target="_blank"
&gt;grlx&lt;/a
&gt; (pronounced &amp;lsquo;garlic&amp;rsquo;) for some time now (since mid-2021).
But why?
What&amp;rsquo;s wrong with SaltStack?
More specifically, what problem am I trying to solve that doesn&amp;rsquo;t already have a solution solved many times over?&lt;/p&gt;
&lt;h4 id="a-note-to-the-salt-project"&gt;A Note to the Salt Project&lt;/h4&gt;
&lt;p&gt;First of all, I want to make something clear: this post is not an attack on Salt, Thomas Hatch, or any of the people involved in the Salt Project.
I&amp;rsquo;ve been a long-time user of SaltStack, going back to the
&lt;a
href="https://docs.saltproject.io/en/latest/topics/releases/2016.3.0.html"
target="_blank"
&gt;Boron release&lt;/a
&gt; in 2016.
SaltStack has gotten me really far, and I owe much of my career&amp;rsquo;s success to Salt, which I have carried with me from job to job.&lt;/p&gt;
&lt;p&gt;I also want to make a call-out here: my use case for Salt is not necessarily the target use case, and it&amp;rsquo;s really unfair to criticize a screwdriver for being a bad hammer, especially when the screwdriver is free.&lt;/p&gt;
&lt;h4 id="real-world-requirements"&gt;Real-world Requirements&lt;/h4&gt;
&lt;p&gt;At my first role as a Salt user, I was not the decision maker.
I was a junior developer, working with the tools selected by peers and higher-ups, and Salt was chosen to be part of our stack.
Without going into too much detail, here was our problem statement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We are building IoT, embedded Linux boards&lt;/li&gt;
&lt;li&gt;We need a way to update the software and packages remotely&lt;/li&gt;
&lt;li&gt;We are using field-prototypes, which don&amp;rsquo;t have fixed or public IPs and therefore need to be the Client in a Client-Server model (agentless solutions won&amp;rsquo;t work)&lt;/li&gt;
&lt;li&gt;We are not just updating application code, our environments are going through rapid updates (emphasis on field &lt;em&gt;prototypes&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Our distribution is a custom Yocto build, without a package manager, so we need a way to declaratively replace system-level packages transactionally&lt;/li&gt;
&lt;li&gt;The ability to remotely run shell commands and return the output is a plus (we might need to trigger fswebcam and snap a photo as a one-off)&lt;/li&gt;
&lt;li&gt;Rolling out instant updates to targeted devices (where a &amp;rsquo;target&amp;rsquo; could be a small sampling, the whole fleet, or a single device) is a huge boon&lt;/li&gt;
&lt;li&gt;Some devices will connect over 3g cellular as not all of our field installations have WiFi available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As this platform was a product of its time, here&amp;rsquo;s some additional information to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The year is 2016, and the hardware development went back to 2012 (so pre-Raspberry Pi GA)&lt;/li&gt;
&lt;li&gt;Google IoT Core doesn&amp;rsquo;t exist (yet, and also,
&lt;a
href="https://www.iotworldtoday.com/connectivity/google-cloud-to-shut-down-iot-core-service"
target="_blank"
&gt;RIP&lt;/a
&gt;)&lt;/li&gt;
&lt;li&gt;Amazon GreenGrass is not generally available (until 2017)&lt;/li&gt;
&lt;li&gt;Ubuntu Core IoT was prohibitively expensive and didn&amp;rsquo;t support our custom hardware&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SaltStack, in theory, checked all of our boxes, and allowed us to ship units to the field for testing, with remote, OTA updates.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Linux support? Check. [x]&lt;/li&gt;
&lt;li&gt;Remote updates using a client / server model to get around the IP contstraints? Check. [x]&lt;/li&gt;
&lt;li&gt;Ability to upgrade system files in addition to application files? Check. [x]&lt;/li&gt;
&lt;li&gt;Remote command execution? Check. [x]&lt;/li&gt;
&lt;li&gt;Specific device targeting? Check. [x]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so, our small team set out to create a configuration management system built on top of SaltStack as the center pillar.
Yes, we even managed the SaltStack installation with SaltStack, by replacing the Python libraries and .egg/wheel files with Salt states.
We were careful in how we did this, and actually added a cron-based backdoor &amp;lsquo;rescue&amp;rsquo; service in case the snake swallowed its own tail, but it was a calculated risk.
In the end, the deployment worked, but it wasn&amp;rsquo;t without headaches.&lt;/p&gt;
&lt;h3 id="issues-and-complaints"&gt;Issues and Complaints&lt;/h3&gt;
&lt;h5 id="dependencies"&gt;Dependencies&lt;/h5&gt;
&lt;p&gt;There were several runtime and compile-time bugs related to the dependencies.
Anybody who&amp;rsquo;s been around SaltStack long enough will shudder at the mention of
&lt;a
href="https://github.com/saltstack/salt/issues?q=tornado"
target="_blank"
&gt;Tornado&lt;/a
&gt; or
&lt;a
href="https://github.com/saltstack/salt/issues?q=pycrypto"
target="_blank"
&gt;PyCrypto&lt;/a
&gt;. We were especially subject to the issues regarding
&lt;a
href="https://github.com/saltstack/salt/issues/52674"
target="_blank"
&gt;pycrypto compilation&lt;/a
&gt;, as our Yocto project build ran on ARMv5 (oh whoops, did I not mention that requirement earlier? That sucks, but we&amp;rsquo;re all engineers here and you just have to learn to roll with it&amp;hellip;) and there weren&amp;rsquo;t precompiled wheels for our architecture&amp;ndash;we had to build them ourselves.
Memory was always running low, as we had less than 1GB of physical memory, and most of that needed to go to the application for some on-device image processing.&lt;/p&gt;
&lt;h5 id="crashing"&gt;Crashing&lt;/h5&gt;
&lt;p&gt;There was a memory leak, somewhere, in either &lt;code&gt;salt-minion&lt;/code&gt; itself, or in a dependency.
It was difficult to isolate, and I&amp;rsquo;m not sure my team actually ever found it.
It was likely patched in a later version of &lt;code&gt;salt-minion&lt;/code&gt; (again, or in a bumped dependency).
Our solution was to
&lt;a
href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html#"
target="_blank"
&gt;limit the memory used by systemd&lt;/a
&gt; and tell the unit to crash and restart if it went over.
This was not ideal.
Systemd had no visibilty into how we were using &lt;code&gt;salt-minion&lt;/code&gt;, whether we were killing the process due to a real memory leak or due to a spike in usage associated with an active software update.
It&amp;rsquo;s reasonable to allocate more memory to &lt;code&gt;salt-minion&lt;/code&gt; and away from our application during a software update, but not at the expense of allowing run-away memory usage during regular runtime.
Allowing systemd to indiscriminately OOM &lt;code&gt;salt-minion&lt;/code&gt; becomes even more dangerous if we are currently applying a state to update the system or python libs themselves&amp;ndash;a crash mid-update for core and critical python libraries might prevent &lt;code&gt;salt-minion&lt;/code&gt; itself from ever coming back up.
In the end, we had to isolate the states for the system by themselves and run those states very selectively, not in the top/high state, which somewhat defeats the purpose of the &amp;lsquo;statefulness&amp;rsquo; Salt brings you&amp;ndash;but that&amp;rsquo;s not Salt&amp;rsquo;s fault, as it certainly wasn&amp;rsquo;t designed for that.
Hammer, screwdriver, all that.
Really the problem here was the memory leak itself, as that&amp;rsquo;s the cascading issue.&lt;/p&gt;
&lt;h5 id="connectivity-issues"&gt;Connectivity issues&lt;/h5&gt;
&lt;p&gt;Memory leaks and crashing aside, we had a lot of trouble keeping the zeroMQ connection active.
Now, we did have many systems connected over 3G cellular, but the problems weren&amp;rsquo;t isolated to those units.
Our WiFi-connected units also had issues maintaining connectivity.
The dreaded &lt;code&gt;Minion did not return. [Not connected]&lt;/code&gt; message was a commonplace error.
More disturbing, early versions of Salt didn&amp;rsquo;t allow you to easily configure the retry logic.
Sometimes, the connection would break, and the client wouldn&amp;rsquo;t ever try to reconnect, so it would stay disconnected until the memory leak would trigger an OOM, which would in-turn restart the &lt;code&gt;salt-minion&lt;/code&gt; daemon.
(
&lt;a
href="https://docs.saltproject.io/en/latest/ref/configuration/minion.html#keepalive-settings"
target="_blank"
&gt;This has since been addressed.&lt;/a
&gt;)&lt;/p&gt;
&lt;h5 id="access-control"&gt;Access control&lt;/h5&gt;
&lt;p&gt;Whoever needed to send the updates out to the units in the field needed to be root (or have sudo access to run &lt;code&gt;salt&lt;/code&gt;) on the Salt master, which means someone needed shell access to the CNC server.
From a security perspective, this is an ick.&lt;/p&gt;
&lt;h4 id="i-loved-saltstack"&gt;I loved SaltStack&lt;/h4&gt;
&lt;p&gt;Yes, I&amp;rsquo;ve had my fair share of problems with SaltStack, but my teams and I made things work.
When working with software of this scale, some
&lt;a
href="https://salt.tips/"
target="_blank"
&gt;tips and tricks&lt;/a
&gt; tend to reveal themselves over time, (like using
&lt;a
href="https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.cp.html#salt.modules.cp.get_template"
target="_blank"
&gt;get_template&lt;/a
&gt; to pre-render salt states before applying).
Sometimes, you just need the
&lt;a
href="https://github.com/saltstack/salt/issues/55269"
target="_blank"
&gt;tribal knowledge gained through experience&lt;/a
&gt;.
Several issues were fixed by the Salt Project team, others have gone away simply through a revision to requirements on my end, and still others I&amp;rsquo;ve learned to live with or work around.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve brought SaltStack with me to 4 other career roles since my introduction to it, like some kind of DevOps Evangelist (I&amp;rsquo;ll likely write about these projects at a later date).
I didn&amp;rsquo;t have enough good things to say about Salt.
I became very involved in the
&lt;a
href="https://saltstackcommunity.slack.com/"
target="_blank"
&gt;community Slack channels&lt;/a
&gt;.
I answered a question or two on
&lt;a
href="https://stackoverflow.com/questions/42519060/how-to-get-minion-id-from-jinja-script-inside-a-salt-state"
target="_blank"
&gt;Stack Overflow&lt;/a
&gt; regarding SaltStack.
I&amp;rsquo;ve had a minor
&lt;a
href="https://github.com/saltstack/salt-bootstrap/pull/1502"
target="_blank"
&gt;PR merged into salt-bootstrap&lt;/a
&gt;.
I&amp;rsquo;ve been featured on the
&lt;a
href="https://www.youtube.com/watch?v=sl3DxxnYf5M"
target="_blank"
&gt;Salt Community Open Hour podcast&lt;/a
&gt;.
The
&lt;a
href="https://saltproject.io/security-announcements/2020-04-21-advisory/index.html"
target="_blank"
&gt;project called me out&lt;/a
&gt; in an advisory bulletin to thank me for my help on
&lt;a
href="https://saltexploit.com"
target="_blank"
&gt;CVE-2020-11651 and CVE-2020-11652&lt;/a
&gt;.&lt;/p&gt;
&lt;h4 id="acquisition-heartache"&gt;Acquisition Heartache&lt;/h4&gt;
&lt;p&gt;In October, 2020, VMware
&lt;a
href="https://blogs.vmware.com/management/2020/10/vmware-completes-saltstack-acquisition-to-bolster-software-configuration-management-and-infrastructure-automation.html"
target="_blank"
&gt;acquired SaltStack&lt;/a
&gt;, and the branding was changed to
&lt;a
href="https://saltproject.io/"
target="_blank"
&gt;Salt Project&lt;/a
&gt;.
Large portions of the team were made redundant (read: laid off) and the focus of the project seems to have shifted to cloud-native and VMware-related projects.
The name change alone causes a bit of a headache.
The
&lt;a
href="https://www.saltproject.org/"
target="_blank"
&gt;Salt Project&lt;/a
&gt; is already a(n entirely unrelated) thing.
Changing from &amp;ldquo;SaltStack&amp;rdquo; to &amp;ldquo;Salt&amp;rdquo; makes finding security issues difficult when
&lt;a
href="https://salt.security/"
target="_blank"
&gt;SALT&lt;/a
&gt; exists.
The overall &amp;ldquo;Googleability&amp;rdquo; of the project has gone downhill, which really isn&amp;rsquo;t great when some of the
&lt;a
href="https://docs.saltproject.io/topics/releases/2014.1.0.html"
target="_blank"
&gt;page redirects are broken&lt;/a
&gt; for
&lt;a
href="https://github.com/saltstack/salt/releases?page=14"
target="_blank"
&gt;all pre-hydrogen releases&lt;/a
&gt;.&lt;/p&gt;
&lt;p&gt;The number of unresolved issues in the project have skyrocketed (even despite the
&lt;a
href="https://github.com/saltstack/salt/issues?q=label%3Astale&amp;#43;is%3Aclosed"
target="_blank"
&gt;stale bot&lt;/a
&gt; doing numbers on these issues).
It seems the reduced team size and increasing other priorities has prevented development from keeping up with security and bugfixes in some dependencies, and the &amp;lsquo;solution&amp;rsquo; is to freeze the updates and vendor the dependencies.
That&amp;rsquo;s right,
&lt;a
href="https://github.com/saltstack/salt/commit/4baea1a97be0389fabe5307d084579134a1f9b7a"
target="_blank"
&gt;until recently&lt;/a
&gt;, SaltStack has been vendoring a copy of the Tornado library, despite
&lt;a
href="https://security.snyk.io/package/pip/tornado"
target="_blank"
&gt;publicly-known CVEs&lt;/a
&gt;.&lt;/p&gt;
&lt;p&gt;Salt has also recently changed their packaging format to
&lt;a
href="https://docs.saltproject.io/salt/install-guide/en/latest/topics/upgrade-to-onedir.html"
target="_blank"
&gt;Onedir&lt;/a
&gt;, effectively vendoring all dependencies inside of a single folder (including python itself).
While this sounds great on the surface, keep in mind it will prevent non-breaking, API-compliant hotfix changes from making it down to the user through their system package managers, so users are now completely at the mercy of the Salt Project&amp;rsquo;s timelines and priorities, and their responsiveness record there is&amp;hellip;not perfect in my opinion.&lt;/p&gt;
&lt;h4 id="where-to-go-from-here"&gt;Where To Go From Here&lt;/h4&gt;
&lt;p&gt;Perhaps now you see why I&amp;rsquo;ve started &lt;a class="grlx"&gt;&lt;/a&gt;
&lt;a
href="https://grlx.dev"
target="_blank"
&gt;grlx&lt;/a
&gt;.
I need to fill a SaltStack-sized hole in my heart; build a tool that solves all the same problems Salt did for me, but also address my own issues with the tool.
&lt;code&gt;grlx&lt;/code&gt; will never have 1:1 feature parity with SaltStack, especially as the Salt Project focus shifts to VMware first-party products and integrations, but for people like me, that&amp;rsquo;s a pro, not a con.
In &lt;code&gt;grlx&lt;/code&gt;, our goal is to support all the things that make SaltStack great&amp;ndash;the extensibility, human-readable configuration as code, lightning fast remote execution&amp;ndash;and do it with a small footprint, secure by default.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s my feeling that SaltStack probably isn&amp;rsquo;t to blame for its memory usage either&amp;ndash;it is written in Python, and that&amp;rsquo;s just the price paid for an interpeted language.
For this reason, among others, &lt;code&gt;grlx&lt;/code&gt; is written in
&lt;a
href="https://go.dev/"
target="_blank"
&gt;Go&lt;/a
&gt;, a compiled language.
Similarly, the connectivity issues are likely not part of the Salt codebase, and are probably part of the zeroMQ dependency.
For &lt;code&gt;grlx&lt;/code&gt;, we&amp;rsquo;re using
&lt;a
href="https://nats.io/"
target="_blank"
&gt;NATS.io&lt;/a
&gt; instead, and we&amp;rsquo;ve even
&lt;a
href="https://github.com/nats-io/nats-server/pull/2341"
target="_blank"
&gt;submitted code upstream&lt;/a
&gt; in an attempt to be good Open Source stewards.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like to follow the development of &lt;a class="grlx"&gt;&lt;/a&gt; grlx, there are several ways you can do so.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have a
&lt;a
href="https://x.com/gogrlx"
target="_blank"
&gt;Twitter/X account here&lt;/a
&gt;.&lt;/li&gt;
&lt;li&gt;We have a
&lt;a
href="https://discord.com/invite/VruAThf"
target="_blank"
&gt;community discord&lt;/a
&gt;!&lt;/li&gt;
&lt;li&gt;You can also star our project
&lt;a
href="https://github.com/gogrlx/grlx"
target="_blank"
&gt;on GitHub&lt;/a
&gt; using the button below:
&lt;br&gt;
&lt;br&gt;
&lt;a class="github-button" href="https://github.com/gogrlx/grlx" data-size="large" data-show-count="true" aria-label="Star gogrlx/grlx on GitHub"&gt;Star&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll also be releasing several posts in the coming weeks going into more detail about the features and roadmap for &lt;a class="grlx"&gt;&lt;/a&gt; grlx, so hit that RSS button at the top of the page to make sure you don&amp;rsquo;t miss anything!&lt;/p&gt;
&lt;script async defer src="https://buttons.github.io/buttons.js"&gt;&lt;/script&gt;</description></item><item><title>Frontend FOMO: A Hype-Driven Ecosystem</title><link>https://blog.taigrr.com/blog/frontend-fomo/</link><pubDate>Tue, 12 Sep 2023 18:19:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/frontend-fomo/</guid><description>&lt;p&gt;Ah, JavaScript development, the ever-evolving frontier of web development.
New runtimes, package managers, libraries, frameworks, module systems, and packages pop up like mushrooms after a rainstorm.
Every week, it feels like a tech carnival, with flashy new tools and configurations tempting you to hop aboard the latest hype train.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by acknowledging that innovation is a positive force for good.
The JavaScript ecosystem is a hotbed of creativity, where developers are constantly pushing the boundaries of what&amp;rsquo;s possible.
New libraries and frameworks emerge to solve real-world problems, making developers&amp;rsquo; lives easier and applications more powerful.&lt;/p&gt;
&lt;p&gt;Innovation itself is not the problem.
The problem is the collective behavior of developers, often resembling a herd of lemmings, that can lead to chaos.
Front-end (and consequently Full-Stack) developers seem to be particularly susceptible to FOMO and analysis paralysis.
Once (or even before) a developer starts to feel comfortable with a tech stack, a new kid on the block arrives, and some begin suddenly questioning every decision they’ve ever made.&lt;/p&gt;
&lt;p&gt;Imagine this scenario: you&amp;rsquo;ve been working with React for a while, building beautiful (let’s be honest, janky but functional) user interfaces, and you&amp;rsquo;re finally getting the hang of it.
But wait, Svelte comes along and promises to revolutionize the way you think about front-end development.
You start to wonder if you&amp;rsquo;ve been living in the Stone Age and if Svelte is the key to enlightenment.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cuHDQhDhvPE?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;Perhaps you&amp;rsquo;re knee-deep in Node.js, happily coding away server-side, when Deno makes its grand entrance, boasting improved security and a new approach to runtime environments.
Suddenly, your Node.js project feels like last year&amp;rsquo;s news, and you can&amp;rsquo;t help but question if Deno is the future.
&lt;i&gt;Nota bene: Bun was released right before this article was published, and its release only makes my point stronger.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;FOMO leads to a never-ending cycle of tool evaluation, configuration tweaking, and second-guessing.
It&amp;rsquo;s like being stuck in a revolving door that never stops spinning.
You find yourself constantly switching between libraries and frameworks, never truly mastering any of them, and your deliverables suffer from analysis paralysis.&lt;/p&gt;
&lt;p&gt;I’ve been picking on Front-end developers, but let’s be fair: backend developers face choices too; I&amp;rsquo;m all too familiar with the Go / Rust / Zig argument (and I’ll save Ruby on Rails and PHP for a future date).
There are so many general-purpose languages (GPLs, as opposed to domain-specific languages, DSLs) that, of course, there is bound to be arguing, evangelizing, and a general lack of consensus.&lt;/p&gt;
&lt;p&gt;However, I would posit the arguments that arise over which backend language to use are of an entirely different class than the arguments that arise within the JavaScript ecosystem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Your average Go developer and Python developer get into an argument over whose language is Better.
What are their talking points?
Typically, the Python developer will criticize Go for having a smaller package ecosystem, and no significant support for Machine Learning, praising Python for ease of development &amp;ndash; it’s often faster to get a prototype out the door for Python than for Go.&lt;/p&gt;
&lt;p&gt;The Go developer will then come back complaining about Python’s slower speed, dynamic typing, and runtime package management.
If both parties are reasonable, they may agree that while both languages are &amp;ldquo;General Purpose,&amp;rdquo; there are still &amp;ldquo;domain preferences&amp;rdquo; for each language.
In other words, there are problems and use cases where one language is the better tool for the job, even if both could work.
(I’m aware the comparison between a compiled and interpreted language may be more extreme than between Go and Rust or Python and Julia, but the point still carries.)&lt;/p&gt;
&lt;p&gt;Now look at an argument between a Vue user and a React user.
The Vue user might complain about JSX syntax, the corporate backing (Meta), and the relaxed project scaffolding (the strict 1:1 component to file rule means all Vue projects end up organized somewhat the same, without requiring developers to search across files to find where a component is defined.)
Conversely, React developers insist ‘the future is React’, more developers know React (hiring talent is easier), and complain the strictness of Vue’s scaffolding leads to verbose, sprawling directories and &lt;code&gt;ReallyLongAndSpecificComponentNames.vue&lt;/code&gt; (getting Java flashbacks, anybody?)&lt;/p&gt;
&lt;p&gt;Of course, I won’t pretend all arguments about these libraries are the same.
React does mobile better (thanks to React Native).
Vue does incremental adoption better.
(React and Vue have gone back and forth on which has better performance.)&lt;/p&gt;
&lt;p&gt;So, while there are Domain arguments to be had across JavaScript frameworks, the point I am making here is that &lt;b&gt;the loudest and oft repeated disagreements over JavaScript stacks are about Developer Experience (DX) and preference, rather than which stack can do the job better in production.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;I still don’t think these arguments over preference are The Problem With The JavaScript Ecosystem™.
Different developers will make judgment calls for one choice over another.
But if the tools can all (mostly) get you to the same, functional result, and it’s really a DX and preferential choice question, FOMO is the problem.
The most important factor for seamless DX is the stability of the ecosystem: building components and shipping changes to production should become muscle memory over time.
&lt;b&gt;This muscle memory can never develop when the developer is constantly switching between tools and libraries.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Backend developers listen to the arguments over which JS stack does what and how.
Inevitably overwhelmed, they retreat back into their caves to write some of the finest, purest Haskell ever conceived, then sit back with a glass of brandy admiring their monads (or something like that, I don’t know, I don’t write Haskell) leaving the Scary Frontend World alone.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s generally acknowledged that Linux &amp;ldquo;distro hopping&amp;rdquo; is an unhinged practice fit for masochists and the clinically deranged.
It&amp;rsquo;s high time we collectively agree stack churn in the JavaScript Ecosystem is a comparable vice.
What is the solution then?
Where’s a good tradeoff between stifling innovation and maintaining stability?&lt;/p&gt;
&lt;p&gt;Be pragmatic.
If you&amp;rsquo;re a library maintainer, keep building awesome tools, but don&amp;rsquo;t expect everyone to use them right away.
There are already so many moving pieces.
If what you&amp;rsquo;re building is truly revolutionary, it will stand the test of time and eventually become a standard (whether you like it or not!).&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re building a product, you need to ship.
Leave the bleeding edge, the framework wars, and library hopping to the maintainers of the tools.
Chances are you don&amp;rsquo;t need to be an expert in every new library that comes out.&lt;/p&gt;
&lt;p&gt;Stop, settle, and &lt;b&gt;pick something to move forward.&lt;/b&gt;
If you can’t decide where to start, then &lt;i&gt;vox populi vox dei&lt;/i&gt;: start with
&lt;a
href="https://nextjs.org/docs"
target="_blank"
&gt;Next.js&lt;/a
&gt;.
Get really good at it.
Only switch up your stack if there’s a feature your platform can’t function without.
It’s time to &lt;b&gt;focus on shipping, not deliberating.&lt;/b&gt;
I regretfully inform you, that even if you can’t take advantage of the latest shiny, you’ll be ok!&lt;/p&gt;</description></item><item><title>Setting up a WireGuard VPN for Remote Access</title><link>https://blog.taigrr.com/blog/provisioning-wireguard/</link><pubDate>Thu, 24 Mar 2022 18:19:25 +0600</pubDate><guid>https://blog.taigrr.com/blog/provisioning-wireguard/</guid><description>&lt;h3 id="what-is-wireguard"&gt;What is WireGuard?&lt;/h3&gt;
&lt;p&gt;WireGuard is a VPN, which tunnels traffic over encrypted, secure UDP packet streams.
Once a tunnel is established, the endpoints are allowed to roam (change IP addresses, such as might happen switching between WiFi networks or when connected to a cell tower).
It is special when compared to other VPN technologies for many technical reasons, which led to its rapid adoption into the Linux Kernel, but three features in particular make WireGuard stand out: the CryptoKey Routing Table, the way public/private keypairs are handled, and the &amp;ldquo;silent by default&amp;rdquo; behavior.&lt;/p&gt;
&lt;p&gt;According to the
&lt;a
href="https://www.wireguard.com/"
target="_blank"
&gt;wireguard website&lt;/a
&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.
It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache.
It intends to be considerably more performant than OpenVPN.
WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances.
Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable.
It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id="wireguard-basics"&gt;WireGuard Basics&lt;/h3&gt;
&lt;p&gt;Unlike traditional VPN technologies, WireGuard has no concept of Client or Server, but instead all nodes on the network are simply &lt;code&gt;Peer&lt;/code&gt;s, connecting Point-to-Point.
This allows for some really interesting scenarios where packets sent through the VPN can be sent through very specific routes by setting IP masks appropriately, and if all &lt;code&gt;Peer&lt;/code&gt;s on the VPN have public IP addresses (or else they can &amp;lsquo;see&amp;rsquo; each other in some other way, perhaps by sharing an insecure local network), the single point of failure built into the Hub-and-Spoke model becomes optional.&lt;/p&gt;
&lt;img
title="image title"
loading="lazy"
decoding="async"
class="img img-fluid img-center"
width="1000"
height="459"
src="https://blog.taigrr.com/images/post/hubAndSpoke_hu_70e8a0cc6eb23c8e.png"
alt="Hub and Spoke vs. P2P"
onerror="this.onerror='null';this.src='\/images\/post\/hubAndSpoke_hu_70e8a0cc6eb23c8e.png'" /&gt;
&lt;p&gt;However, even if WireGuard doesn&amp;rsquo;t specifically recognize the Hub and Spoke model, it&amp;rsquo;s still a valid configuration,and we&amp;rsquo;ll be using it for this tutorial.
Let&amp;rsquo;s see how easy it is to configure a Hub and Spoke-style, simple WireGuard VPN ready for personal or commercial use.&lt;/p&gt;
&lt;h4 id="server-instructions"&gt;Server Instructions&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Choose a private IP block to reserve.
In order to use WireGuard inside of a Spoke and Hub paradigm, it&amp;rsquo;s necessary to reserve a block of IP addresses for all the nodes.
(When running Peer to Peer, it&amp;rsquo;s not necessary for these addresses to share a subnet, as a CIDR of /32 can be used for each peer.)
Private IP ranges include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;10.0.0.0&lt;/code&gt; to &lt;code&gt;10.255.255.255&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;172.16.0.0&lt;/code&gt; to &lt;code&gt;172.31.255.255&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;192.168.0.0&lt;/code&gt; to &lt;code&gt;192.168.255.255&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An example range for WireGuard might look like: &lt;code&gt;192.168.2.1-192.168.2.255&lt;/code&gt; (be sure this range doesn&amp;rsquo;t conflict with that of your router).
In CIDR notation, this range would be: &lt;code&gt;192.168.2.0/24&lt;/code&gt;.
You can use a
&lt;a
href="https://www.subnet-calculator.com/cidr.php"
target="_blank"
&gt;CIDR Calculator&lt;/a
&gt; to determine the proper format for a given range.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose a port for your tunneled traffic to use.&lt;/strong&gt;
WireGuard traffic flows over UDP, and by default uses port &lt;code&gt;51820&lt;/code&gt;.
For this tutorial, we will assume you are using the default port, but if you are running multiple WireGuard VPNs on the same server, you will need to choose a different port for each one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose a cloud provider that provides you with a public IP address.&lt;/strong&gt;
My personal preference is
&lt;a
href="https://www.linode.com/?r=25e315c408f1c8da14e13d91a59e2e11e81ac639"
target="_blank"
&gt;Linode/Akamai&lt;/a
&gt; (by using my signup link you&amp;rsquo;ll get free credits, as will I.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you are ready to spin up a server, &lt;strong&gt;be sure to choose a distribution/flavor which ships Linux 5.6 or above.&lt;/strong&gt;
(Some versions of Ubuntu with older kernels also received backports, but if you have the option, go with the latest stable release available.)
It is possible to use WireGuard in Userspace (if you don&amp;rsquo;t know what this means, don&amp;rsquo;t worry about it, just use a newer installation image) but it is much slower than the version running in the kernel.
Going forward, we will assume you have the public IP of &lt;code&gt;93.184.216.34&lt;/code&gt; (used for
&lt;a
href="https://example.com"
target="_blank"
&gt;example.com&lt;/a
&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install WireGuard and its helper utilities.&lt;/strong&gt;
The name of the helper utilities package is usually &lt;code&gt;wireguard-tools&lt;/code&gt;, and installing that package should bring in &lt;code&gt;wireguard&lt;/code&gt; as well.
If you don&amp;rsquo;t already have &lt;code&gt;iptables&lt;/code&gt; installed, you will need to install that as well.
(On Debian-based systems, run &lt;code&gt;apt install wireguard-tools iptables&lt;/code&gt;.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable IP forwarding.&lt;/strong&gt;
This is a necessary step for the server to act as a router.
On Linux, this is done by running &lt;code&gt;sysctl -w net.ipv4.ip_forward=1&lt;/code&gt;.
To make this change permanent, edit or create &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; and add the line &lt;code&gt;net.ipv4.ip_forward=1&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a keypair for your server.&lt;/strong&gt;
Once WireGuard is installed, you can generate a private key by running &lt;code&gt;wg genkey&lt;/code&gt;.
To get the public key, run &lt;code&gt;wg pubkey&lt;/code&gt; and paste (or pipe) in the private key.
This keypair is just like an SSH public/private keypair, so keep it safe!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use the keypairs to create a configuration file.&lt;/strong&gt;
The next step is to create a configuration file for your server.
There are three main pieces of information required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the interface and port to listen on&lt;/li&gt;
&lt;li&gt;the WireGuard private IP and netmask (CIDR Notation) you are committing to use (such as &lt;code&gt;192.168.2.1/24&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;the Private Key of your server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a file at &lt;code&gt;/etc/wireguard/my-vpn.conf&lt;/code&gt; and add in the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Interface]
ListenPort = 51820
PrivateKey = &amp;lt;SERVER PRIVATE KEY HERE&amp;gt; # &amp;lt;SERVER PUBLIC KEY HERE&amp;gt;
Address = 192.168.2.1/24
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Many systems use &lt;code&gt;eth0&lt;/code&gt; as the name of the primary network interface, but if yours is different, you will need to change it in the &lt;code&gt;PostUp&lt;/code&gt; and &lt;code&gt;PostDown&lt;/code&gt; commands.
If you are unsure of your default gateway&amp;rsquo;s interfacve name, run &lt;code&gt;ip a&lt;/code&gt; and look for the interface with the public IP address you are using to connect to the server.&lt;/p&gt;
&lt;p&gt;Additionally, you will add a block for each &lt;code&gt;Peer&lt;/code&gt;, but we&amp;rsquo;ll need to generate those keypairs first.
More on that later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be sure the connection port is exposed through your hosting provider (for UDP traffic).&lt;/strong&gt;
Instructions for Linode can be found
&lt;a
href="https://www.linode.com/docs/products/networking/cloud-firewall/guides/"
target="_blank"
&gt;here&lt;/a
&gt;.
You may need to consult your provider&amp;rsquo;s documentation for more information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Before we start up our server, we need some more information from our clients.
We&amp;rsquo;ll come back to the server later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="client-instructions"&gt;Client Instructions&lt;/h4&gt;
&lt;h5 id="linux-instructions"&gt;Linux instructions:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Ensure your kernel has support for WireGuard, or install the userspace version.
On Ubuntu/Debian-based systems, run &lt;code&gt;apt install wireguard-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create the folder &lt;code&gt;/etc/wireguard/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Generate a new keypair the same way you did on the server:
&lt;code&gt;wg genkey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Place the keypair into a config file named &lt;code&gt;my-vpn.conf&lt;/code&gt; and save it, along with the details you got from the server.
The format is as follows:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Interface]
PrivateKey = &amp;lt;CLIENT PRIVATE KEY HERE&amp;gt; # &amp;lt;CLIENT PUBLIC KEY HERE&amp;gt;
Address = 192.168.2.4/24 # Or whatever IP you want your client to use on the VPN
[Peer]
PublicKey = &amp;lt;SERVER PUBLIC KEY HERE&amp;gt;
AllowedIPs = 192.168.2.0/24 # This is the subnet you chose for your VPN, and should match the server&amp;#39;s
Endpoint = 93.184.216.34:51820 # This is the public IP of your server, and the port you chose
# Note you can also use a domain name, if you want:
# Endpoint = example.com:51820
PersistentKeepalive = 25 # allow your client to roam without dropping the connection
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;chmod&lt;/code&gt; the config file and the &lt;code&gt;/etc/wireguard&lt;/code&gt; directory to &lt;code&gt;400&lt;/code&gt; and &lt;code&gt;chown&lt;/code&gt; it to &lt;code&gt;root:root&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On systemd-based systems, run &lt;code&gt;systemctl enable wg-quick@my-vpn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Move on to the server section, when complete move on to the next step.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id="mac-instructions"&gt;Mac instructions:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Install
&lt;a
href="https://apps.apple.com/us/app/wireguard/id1451685025?mt=12"
target="_blank"
&gt;Wireguard for Mac&lt;/a
&gt;&lt;/li&gt;
&lt;li&gt;Follow prompts to fill in the info from your server.&lt;/li&gt;
&lt;li&gt;Choose an IP from the subnet you chose for your VPN.&lt;/li&gt;
&lt;li&gt;Save your Public Key for later.&lt;/li&gt;
&lt;li&gt;Enable start on demand.&lt;/li&gt;
&lt;li&gt;Complete the server steps next, before hitting &lt;code&gt;Save&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="final-server-instructions"&gt;Final Server Instructions&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Inside of your &lt;code&gt;my-vpn.conf&lt;/code&gt; file, create a &lt;code&gt;Peer&lt;/code&gt; block following the pattern of the previous entries:
&lt;pre tabindex="0"&gt;&lt;code&gt;[Peer] # name of client
PublicKey = &amp;lt;CLIENT PUBLIC KEY HERE&amp;gt;
AllowedIPs = 192.168.2.&amp;lt;n&amp;gt;/32 # replace `&amp;lt;n&amp;gt;` with the last octet of the Client&amp;#39;s WireGuard private IP
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Save and close the config file.&lt;/li&gt;
&lt;li&gt;Ensure the config file is owned by &lt;code&gt;root&lt;/code&gt; and has permissions of &lt;code&gt;400&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;systemctl enable --now wg-quick@my-vpn&lt;/code&gt; to start the WireGuard tunnel and set it to start automatically on boot.&lt;/li&gt;
&lt;li&gt;Continue setting up from the client side.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id="linux"&gt;Linux&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Same as for the server, run &lt;code&gt;systemctl enable --now wg-quick@my-vpn&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validate the VPN is running with &lt;code&gt;wg&lt;/code&gt;.
You should see something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;interface: my-vpn
public key: &amp;lt;CLIENT PUBLIC KEY HERE&amp;gt;
private key: (hidden)
listening port: 58341
peer: &amp;lt;SERVER PUBLIC KEY HERE&amp;gt;
endpoint: 93.184.216.34:51820
allowed ips: 192.168.2.0/24
latest handshake: 13 seconds ago
transfer: 5.37 MiB received, 6.40 MiB sent
persistent keepalive: every 25 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you see an error, check the logs with &lt;code&gt;journalctl -u wg-quick@my-vpn&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id="macos"&gt;MacOS&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Hit &lt;code&gt;Save&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Optionally, in your Mac&amp;rsquo;s System Preferences, go to &lt;code&gt;Network&lt;/code&gt; and check the box for &lt;code&gt;Enable VPN on Demand&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="next-steps"&gt;Next Steps&lt;/h3&gt;
&lt;p&gt;You should now have a working WireGuard VPN, ready for roaming and personal use.
There&amp;rsquo;s only one client for now, but you can add as many as you like by repeating the client steps on each new device. (Be sure to pick a unique private IP address inside of the subnet you chose for your VPN for each client.)
Then, you can jump directly from one client to another using the WireGuard private IP addresses.
This can be particularly useful when pairing the
&lt;a
href="https://play.google.com/store/apps/details?id=com.wireguard.android&amp;amp;hl=en_US&amp;amp;gl=US"
target="_blank"
&gt;WireGuard app for Android&lt;/a
&gt; with
&lt;a
href="https://termux.com/"
target="_blank"
&gt;Termux&lt;/a
&gt; or
&lt;a
href="https://juicessh.com/"
target="_blank"
&gt;JuiceSSH&lt;/a
&gt; to access your laptop or desktop on the go.&lt;/p&gt;
&lt;p&gt;Once the client side setup is done, simply add a &lt;code&gt;Peer&lt;/code&gt; block for each client in the server&amp;rsquo;s config file, using a unique private IP address, then run &lt;code&gt;systemctl restart wg-quick@my-vpn&lt;/code&gt; on the server to restart the tunnel with the new configuration.
It is normal for the tunnel to drop when you run the restart command, but it should come back up within a few seconds.&lt;/p&gt;
&lt;p&gt;If you have any issues, check the logs with &lt;code&gt;journalctl -u wg-quick@my-vpn&lt;/code&gt;.
If you&amp;rsquo;re stumped, feel free to reach out to the community in my
&lt;a
href="https://discord.gg/FWvDANDvaP"
target="_blank"
&gt;Discord&lt;/a
&gt;, and someone may be able to help you out.&lt;/p&gt;
&lt;h3 id="going-further"&gt;Going Further&lt;/h3&gt;
&lt;p&gt;If you want to take your WireGuard VPN to the next level, you can add a DNS server to your server&amp;rsquo;s config file, and then add a &lt;code&gt;DNS&lt;/code&gt; entry to each client&amp;rsquo;s &lt;code&gt;Interface&lt;/code&gt; block.
You can read more about that
&lt;a
href="https://readtfm.org/title/WireGuard#DNS"
target="_blank"
&gt;here&lt;/a
&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to use WireGuard to route all of your traffic through the VPN, pair WireGuard with netns or Docker containers to force applications to use the VPN, or even use WireGuard to create a mesh network.
I&amp;rsquo;ll likely be writing more about these topics in the future.
For now, there are great resources available on the
&lt;a
href="https://www.wireguard.com/"
target="_blank"
&gt;WireGuard website&lt;/a
&gt; and on YouTube.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&amp;ldquo;WireGuard&amp;rdquo; and the &amp;ldquo;WireGuard&amp;rdquo; logo are registered trademarks of Jason A. Donenfeld.&lt;/small&gt;&lt;/p&gt;</description></item></channel></rss>