<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Buchatech.com</title>
	<atom:link href="https://www.buchatech.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.buchatech.com</link>
	<description>A blog about Cloud, Containers, Kubernetes, GitOps, DevOps and more</description>
	<lastBuildDate>Tue, 10 Mar 2026 05:33:08 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.buchatech.com/wp-content/uploads/2021/10/cropped-favicon-32x32-1-32x32.png</url>
	<title>Buchatech.com</title>
	<link>https://www.buchatech.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>My 30th Course: Google Firebase Studio Foundations (Vibe Coding)</title>
		<link>https://www.buchatech.com/2026/03/my-30th-course-google-firebase-studio-foundations/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 18:55:50 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[IT Career]]></category>
		<category><![CDATA[IT Training]]></category>
		<category><![CDATA[Pluralsight]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[App Dev]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Cloud Run]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Firebase]]></category>
		<category><![CDATA[FUll Stack]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Vibe Coding]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9539</guid>

					<description><![CDATA[Ive reached a milestone with my 30th course recently published on Pluralsight. This course is titled Google Firebase Studio Foundations. This was a course topic I suggested to the teams at Pluralsight since Vibe Coding is seeing so much growth and this solution is used for that. It is my 6th AI related course. Firebase ... <a title="My 30th Course: Google Firebase Studio Foundations (Vibe Coding)" class="read-more" href="https://www.buchatech.com/2026/03/my-30th-course-google-firebase-studio-foundations/" aria-label="Read more about My 30th Course: Google Firebase Studio Foundations (Vibe Coding)">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Ive reached a milestone with my 30th course recently published on Pluralsight. This course is titled <strong>Google Firebase Studio Foundations</strong>. This was a course topic I suggested to the teams at Pluralsight since <strong>Vibe Coding</strong> is seeing so much growth and this solution is used for that. It is my 6th AI related course. Firebase Studio is Google’s full stack AI-powered development environment that streamlines the process of prototyping and building apps from idea to deployment. </p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="381" src="https://www.buchatech.com/wp-content/uploads/2026/03/image-1024x381.png" alt="" class="wp-image-9540" srcset="https://www.buchatech.com/wp-content/uploads/2026/03/image-1024x381.png 1024w, https://www.buchatech.com/wp-content/uploads/2026/03/image-300x112.png 300w, https://www.buchatech.com/wp-content/uploads/2026/03/image-768x286.png 768w, https://www.buchatech.com/wp-content/uploads/2026/03/image.png 1458w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>In this course, Google Firebase Studio Foundations, you’ll start by learning the basics of vibe coding with Firebase Studio. First, you&#8217;ll explore how the Gemini AI Agent fits into the development workflow. Next, you’ll discover how to speed up backend, frontend, and mobile app development with AI assistance. Finally, you’ll take an app idea from concept to a working deployment on Firebase App Hosting. By the end of this course, you’ll have the skills needed to confidently use Firebase Studio to build and run modern apps.</p>



<p>I brought this topic forward because I was excited about the opportunity to author a course that showcases what Firebase Studio can do in the vibe coding space. I also wanted to raise awareness about the platform since it can be used for free, and developers can expand to a generous number of workspaces at no cost through a Google Developers account. I packed this course with demos as we work through vibe coding an app. </p>



<p></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="592" src="https://www.buchatech.com/wp-content/uploads/2026/03/image-2-1024x592.png" alt="" class="wp-image-9547" srcset="https://www.buchatech.com/wp-content/uploads/2026/03/image-2-1024x592.png 1024w, https://www.buchatech.com/wp-content/uploads/2026/03/image-2-300x173.png 300w, https://www.buchatech.com/wp-content/uploads/2026/03/image-2-768x444.png 768w, https://www.buchatech.com/wp-content/uploads/2026/03/image-2-1536x888.png 1536w, https://www.buchatech.com/wp-content/uploads/2026/03/image-2-2048x1183.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>This course is ideal for beginners and aspiring developers who want to prototype, build and deploy apps with Google Firebase Studio. Ideal learners include students, early-stage founders, and tech professionals curious about AI-assisted development.</p>



<p>These are the topics in the course:</p>



<h3 class="wp-block-heading">Get Started with Firebase Studio</h3>



<ul class="wp-block-list">
<li>Intro and Overview</li>



<li>Introduction to Vibe Coding</li>



<li>Introduction to Firebase Studio</li>



<li>Demo: Exploring Firebase Studio</li>
</ul>



<h3 class="wp-block-heading">Development with Firebase Studio</h3>



<ul class="wp-block-list">
<li>Intro and Overview</li>



<li>Accelerating Development with Vibe Coding</li>



<li>Demo: Generating a Full App with the Firebase Prototyper</li>
</ul>



<h3 class="wp-block-heading">From Idea to Running App with Firebase Studio</h3>



<ul class="wp-block-list">
<li>Vibe Code to Deployment</li>



<li>What Is Firebase App Hosting?</li>



<li>Deploying the App&nbsp;| 6m</li>



<li>Demo Part 1: Deploy App to Firebase App Hosting</li>



<li>Demo Part 2: Deploy App to Firebase App Hosting</li>
</ul>



<p>If you need to build a web or mobile app, whether you know how to code or not, you will want to check out my new course here: <a href="https://www.pluralsight.com/courses/google-firebase-studio-foundations" target="_blank" rel="noreferrer noopener">https://www.pluralsight.com/courses/google-firebase-studio-foundations</a>.</p>



<p>I hope this course serves as a valuable resource in your Vibe Coding, AI, and app building journey. Thank you for your continued support, and&nbsp;<strong>Be sure to follow my profile on Pluralsight so you will be notified as I release new courses</strong>!&nbsp;</p>



<p>Here is the link to my&nbsp;<strong>Pluralsight profile to follow me</strong>:</p>



<p><a href="https://www.pluralsight.com/authors/steve-buchanan" target="_blank" rel="noreferrer noopener">https://www.pluralsight.com/authors/steve-buchanan</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>Update</strong></p>



<p>I posted about this milestone on LinkedIn. Something really cool happened. The former CEO and founder of Pluralsight <a href="https://www.linkedin.com/in/skonnard" target="_blank" rel="noreferrer noopener">Aaron Skonnard</a> commented on the post congratulating me. This means a lot coming from the founder of Pluralsight.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="542" height="75" src="https://www.buchatech.com/wp-content/uploads/2026/03/image-3.png" alt="" class="wp-image-9556" srcset="https://www.buchatech.com/wp-content/uploads/2026/03/image-3.png 542w, https://www.buchatech.com/wp-content/uploads/2026/03/image-3-300x42.png 300w" sizes="(max-width: 542px) 100vw, 542px" /></figure>



<p></p>



<p></p>



<p>The link to the post is here if you want to check it out: <a href="https://www.linkedin.com/feed/update/urn:li:activity:7436863573412335617/" target="_blank" rel="noreferrer noopener">https://www.linkedin.com/feed/update/urn:li:activity:7436863573412335617</a>. <br></p>



<span id="more-9539"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Bridging the Clouds: Back on RunAs Radio</title>
		<link>https://www.buchatech.com/2026/02/bridging-the-clouds-back-on-runas-radio/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 07:41:16 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Azure Kubernetes Service]]></category>
		<category><![CDATA[Azure Management]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[GitOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[OpenTofu]]></category>
		<category><![CDATA[Platform Engineering]]></category>
		<category><![CDATA[Terraform]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[AKS]]></category>
		<category><![CDATA[DockerCaptain]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[GKE]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Jamf]]></category>
		<category><![CDATA[MicrosoftMVP]]></category>
		<category><![CDATA[Multi-cloud]]></category>
		<category><![CDATA[Okta]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9532</guid>

					<description><![CDATA[It’s hard to believe, but it’s been a couple of years since I last sat down with Richard Campbell on RunAs Radio. Technology moves fast, but the cloud landscape has matured in ways that were only just beginning during my last visit. I recently joined Richard for my third appearance on the show (Episode #1025) ... <a title="Bridging the Clouds: Back on RunAs Radio" class="read-more" href="https://www.buchatech.com/2026/02/bridging-the-clouds-back-on-runas-radio/" aria-label="Read more about Bridging the Clouds: Back on RunAs Radio">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It’s hard to believe, but it’s been a couple of years since I last sat down with Richard Campbell on <strong>RunAs Radio</strong>. Technology moves fast, but the cloud landscape has matured in ways that were only just beginning during my last visit.</p>



<p>I recently joined Richard for my third appearance on the show (Episode #1025) to talk about a challenge that is becoming the &#8220;new normal&#8221; for major SaaS providers: <strong>Expanding a Cloud-Native stack across multiple clouds.</strong></p>



<h3 class="wp-block-heading">From Single-Cloud Roots to Multi-Cloud Reality</h3>



<p>At Jamf, we’ve built a powerful reputation for managing Apple devices at scale. Historically, our SaaS product was rooted in AWS. However, as our customer base grows, now serving over 70k+ customers worldwide the demand for flexibility grows with it.</p>



<p>In this episode, we discuss the journey of bringing those SaaS workloads to <strong>Azure</strong> and <strong>AKS</strong>. It isn’t just about &#8220;moving&#8221; code; <strong><em>it’s about architecting for consistency without losing the unique benefits of each cloud provider</em></strong>.</p>



<h3 class="wp-block-heading">Kubernetes: The Common Ground (But Not the Whole Story)</h3>



<p>One of the key takeaways from our chat is that while <strong>Kubernetes</strong> (AKS, EKS, GKE) provides the common operating system for the modern cloud, it isn&#8217;t a &#8220;magic wand&#8221; for multi-cloud.</p>



<p>To achieve true consistency, you have to look past the orchestrator and focus on the surrounding ecosystem. We dove into the complexities of:</p>



<ul class="wp-block-list">
<li><strong>IaC &amp; Deployment:</strong> Why tools like <strong>OpenTofu</strong> are becoming essential for maintaining cloud-agnostic deployments.</li>



<li><strong>Observability:</strong> Using <strong>Prometheus and Grafana</strong> to ensure that your SRE teams see the same data regardless of whether the backend is Azure or AWS.</li>



<li><strong>Identity:</strong> Navigating the friction between different identity providers to ensure a seamless experience for the end user and how platforms like Okta support this.</li>
</ul>



<h3 class="wp-block-heading">The Docker &amp; AI Connection</h3>



<p>We couldn&#8217;t have a conversation in 2026 without touching on the elephant in the room: <strong>AI</strong>. As a Microsoft MVP focused on AKS and a Docker Captain, I’ve been watching closely how the Kubernetes and container ecosystem is evolving to support AI/ML workloads. Richard and I spent some time discussing how <strong>Docker, Inc.</strong> is positioning itself in this space and how developers can leverage these tools to build AI-ready applications without getting locked into a single vendor&#8217;s proprietary stack.</p>



<h3 class="wp-block-heading">Reflections on a Maturing Landscape</h3>



<p>Coming back to RunAs Radio for a third time allowed me to reflect on just how much our industry has shifted. We’ve moved past the &#8220;is the cloud safe?&#8221; phase and into the &#8220;how do we optimize for a multi-cloud world?&#8221; phase.</p>



<p>Whether you are a platform engineer, a developer, or a technical leader, the lessons I’ve learned at Accenture, Microsoft, helping startups, and now at Jamf while scaling across multple clouds are applicable to almost any modern enterprise.</p>



<p><strong>You can listen to the full episode here:</strong> <a target="_blank" rel="noreferrer noopener" href="https://runasradio.com/Shows/Show/1025">RunAs Radio #1025: SaaS on Multiple Clouds with Steve Buchanan</a></p>



<p>I’d love to hear your thoughts. Is your organization looking at multi-cloud for SaaS, or are you doubling down on a single provider? </p>



<span id="more-9532"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Speaking at Open Source North 2025 on Multi-Cloud</title>
		<link>https://www.buchatech.com/2026/02/speaking-at-open-source-north-2025-on-multi-cloud/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 07:28:35 +0000</pubDate>
				<category><![CDATA[Argo CD]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[GitOps]]></category>
		<category><![CDATA[IT Career]]></category>
		<category><![CDATA[IT Training]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[OpenTofu]]></category>
		<category><![CDATA[Terraform]]></category>
		<category><![CDATA[AKS]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[Jamf]]></category>
		<category><![CDATA[K8s]]></category>
		<category><![CDATA[MNTech]]></category>
		<category><![CDATA[opensource]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9526</guid>

					<description><![CDATA[I am excited to share that I will be speaking at this year&#8217;s Open Source North conference on May 29, 2025, at the University of St. Thomas in St. Paul. This year, I’m teaming up with my fellow Jamf, Levi McCormick (Director of Engineering at Jamf), for a session that is very close to our ... <a title="Speaking at Open Source North 2025 on Multi-Cloud" class="read-more" href="https://www.buchatech.com/2026/02/speaking-at-open-source-north-2025-on-multi-cloud/" aria-label="Read more about Speaking at Open Source North 2025 on Multi-Cloud">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I am excited to share that I will be speaking at this year&#8217;s <strong><a href="https://opensourcenorth.com/" target="_blank" rel="noreferrer noopener">Open Source North</a></strong> conference on May 29, 2025, at the University of St. Thomas in St. Paul.</p>



<p>This year, I’m teaming up with my fellow Jamf, Levi McCormick (Director of Engineering at Jamf), for a session that is very close to our daily reality: <strong>Multi-Cloud Without the Marketing or Designing for Multi-Cloud Without Losing Your Mind.</strong></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="424" src="https://www.buchatech.com/wp-content/uploads/2026/02/image-3-1024x424.png" alt="" class="wp-image-9528" style="width:764px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2026/02/image-3-1024x424.png 1024w, https://www.buchatech.com/wp-content/uploads/2026/02/image-3-300x124.png 300w, https://www.buchatech.com/wp-content/uploads/2026/02/image-3-768x318.png 768w, https://www.buchatech.com/wp-content/uploads/2026/02/image-3.png 1402w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Why this talk?</strong> In the cloud industry, &#8220;Multi-Cloud&#8221;, &#8220;Cloud Native&#8221;, and &#8220;Iac via Terraform&#8221; are often sold as  magic pills for redundancy, cost savings, unifaction and more across clouds. But for the people actually building and maintaining these systems, it can often feel like a recipe for complexity and technical debt.</p>



<p>At Jamf, Levi and I work on our infrastructure efforts across AWS, Azure, and GCP. We’ve learned—sometimes the hard way—what works, what doesn&#8217;t, and where the &#8220;hype&#8221; version of cloud differs from the &#8220;production&#8221; version. We wanted to build a session that focuses on the practical:</p>



<ul class="wp-block-list">
<li>How to design for portability without over-engineering.</li>



<li>Managing identity, networking, and security across different providers.</li>



<li>Avoiding the &#8220;lowest common denominator&#8221; trap.</li>



<li>Keeping your sanity while managing three different clouds.</li>
</ul>



<p>Open Source North is a great local event to the MN Tech scene because of the high-caliber community and the focus on real-world engineering. Whether you are a cloud veteran or just starting to look at a second provider, we’d love to see you there.</p>



<p><strong>The Details:</strong></p>



<ul class="wp-block-list">
<li><strong>Conference:</strong> Open Source North 2025</li>



<li><strong>Date:</strong> May 29, 2025</li>



<li><strong>Location:</strong> University of St. Thomas (St. Paul Campus)</li>



<li><strong>Session Link:</strong> <a href="https://opensourcenorth.com/presentations/Multi-Cloud-Without-the-Marketing-or-Designing-for-Multi-Cloud-Without-Losing-Your-Mind" target="_blank" rel="noreferrer noopener">https://opensourcenorth.com/presentations/Multi-Cloud-Without-the-Marketing-or-Designing-for-Multi-Cloud-Without-Losing-Your-Mind</a></li>
</ul>



<p>If you&#8217;re attending, please connect on LinkedIn or find us after the session. We’d love to hear how your team is tackling these same challenges!</p>



<span id="more-9526"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>State of App Dev Report by Docker</title>
		<link>https://www.buchatech.com/2026/02/state-of-app-dev-report-by-docker/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Thu, 05 Feb 2026 22:53:38 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[K8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9518</guid>

					<description><![CDATA[As devs, platform engineers, and DevOps practitioners, we all feel it: the pace of change is relentless. New tools, new architectures, new expectations, and AI. It can be hard to separate where to invest our time from hype. That’s exactly why I want decided to write this post about the 2025 Docker State of Application ... <a title="State of App Dev Report by Docker" class="read-more" href="https://www.buchatech.com/2026/02/state-of-app-dev-report-by-docker/" aria-label="Read more about State of App Dev Report by Docker">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>As devs, platform engineers, and DevOps practitioners, we all feel it: the pace of change is relentless. New tools, new architectures, new expectations, and AI. It can be hard to separate where to invest our time from hype.</p>



<p>That’s exactly why I want decided to write this post about the<strong> </strong>2025 Docker State of Application Development Report from Docker.</p>



<p>This report is not marketing fluff. It’s based on insights from over 4,500 developers and engineering professionals and offers a grounded snapshot of how application development is actually evolving today. </p>



<p>Although published in 2025, this report covers long-running trends that continue to shape modern application development. Areas like containerized workflows, cloud-based development environments, AI-assisted tooling, and shared responsibility for security evolve over time rather than changing overnight.</p>



<p>Referencing the 2025 report ahead of the 2026 release provides valuable context. It establishes a baseline for understanding where the industry is coming from, which patterns are proving durable, and which challenges continue to persist. I&#8217;ll be looking out for the 2026 report. If you havent checked it out the 2025 report yet you should. </p>



<p>As a Docker Captain, I strongly encourage you to read the full report. But first, here are some of the key takeaways that stood out to me:</p>



<h2 class="wp-block-heading">Remote-First Development Is Becoming the New Normal</h2>



<p>One of the biggest shifts in 2025 is how developers are working:</p>



<ul class="wp-block-list">
<li><strong>64% of developers now use non-local development environments as their primary setup</strong></li>



<li>Only <strong>36% rely primarily on local machines</strong></li>
</ul>



<p>That’s a significant change from previous years, and it speaks to the reality that cloud-based workflows, remote dev environments, and tools that unify development environments are now mainstream. This shift isn’t just a trend — it’s redefining how teams collaborate and deliver software efficiently.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Developer Productivity Still Faces Friction Points</h2>



<p>The report highlights that, despite improvements in tooling and culture, many teams still experience bottlenecks in everyday work:</p>



<ul class="wp-block-list">
<li>Pull requests stuck in review</li>



<li>Tasks without clear estimates</li>



<li>Slowdowns in the “inner development loop”</li>
</ul>



<p>Even with great culture and tooling, friction still exists, especially around planning and execution. Knowing where dev productivity stalls helps us focus improvements where they matter most.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Learning Is Shifting to Self-Guided, Online Resources</h2>



<p>Developers are reinventing how they learn:</p>



<ul class="wp-block-list">
<li><strong>85% of respondents use online courses or certifications</strong></li>



<li>Traditional sources like books or on-the-job training are less dominant</li>
</ul>



<p>This highlights a bigger trend in continuous learning and self-driven skill development — especially important as the pace of change in languages, platforms, and architectures continues to accelerate.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">AI Adoption Is Real, But Not Uniform</h2>



<p>AI continues to influence how software is built, but adoption is still uneven:</p>



<ul class="wp-block-list">
<li>Some teams are deeply integrating AI tools</li>



<li>Others are more cautious or selective</li>
</ul>



<p>The report frames AI as an enabler, not a magic bullet. Developers are using AI to assist with documentation, research, and repetitive tasks, but real productivity gains depend on meaningful integration into workflows and data quality.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Security Is a True Team Effort</h2>



<p>Security is no longer siloed:</p>



<ul class="wp-block-list">
<li>Teams of all sizes report that developers, leads, and operations are involved in security</li>



<li>Only a small fraction of organizations outsource security entirely</li>
</ul>



<p>The idea that “security is someone else’s job” is gone — fixing vulnerabilities and embedding security thinking into the development lifecycle is now a collective responsibility.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What This All Means for Developers</h2>



<p>Taken together, these findings show a software landscape that’s:</p>



<ul class="wp-block-list">
<li>More distributed and cloud-native</li>



<li>More self-taught and adaptable</li>



<li>More collaborative around security</li>



<li>Still facing persistent productivity barriers</li>
</ul>



<p>These trends have real implications for how we build teams, invest in tooling, and think about developer experience.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Go Read the Full Report</h2>



<p>The <em>2025 Docker State of Application Development Report</em> is packed with additional insights, data, and analysis. Whether you’re a developer curious about AI adoption, a manager thinking about remote workflows, or a team lead prioritizing security practices, there’s something in this report for you.</p>



<p><strong>Check out the full report on Docker’s blog:</strong><br><a href="https://www.docker.com/blog/2025-docker-state-of-app-dev/?utm_source=chatgpt.com" target="_blank" rel="noreferrer noopener">https://www.docker.com/blog/2025-docker-state-of-app-dev</a></p>



<span id="more-9518"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Azure Hub-and-Spoke Architecture Explained and Automated with OpenTofu</title>
		<link>https://www.buchatech.com/2026/01/azure-hub-and-spoke-architecture-explained-and-automated-with-opentofu/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Fri, 09 Jan 2026 09:11:21 +0000</pubDate>
				<category><![CDATA[ARM Templates]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Azure Management]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[OpenTofu]]></category>
		<category><![CDATA[Terraform]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[Bicep]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Cloud Native]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Jamf]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9489</guid>

					<description><![CDATA[This is my first blog of the new year (2026)! Since being re-awarded as a Microsoft MVP, Microsoft provided me with a fresh set of Azure credits. One of the first things I wanted to do was rebuild my Azure lab environment. This time, I wanted to do it the right way. I wanted it ... <a title="Azure Hub-and-Spoke Architecture Explained and Automated with OpenTofu" class="read-more" href="https://www.buchatech.com/2026/01/azure-hub-and-spoke-architecture-explained-and-automated-with-opentofu/" aria-label="Read more about Azure Hub-and-Spoke Architecture Explained and Automated with OpenTofu">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is my first blog of the new year (2026)! Since being re-awarded as a Microsoft MVP, Microsoft provided me with a fresh set of Azure credits. One of the first things I wanted to do was rebuild my Azure lab environment. This time, I wanted to do it the right way. I wanted it to mirror how I would design and deploy a real enterprise environment, including running fully on private endpoints and following a proper hub-and-spoke network model.</p>



<p>Just as importantly, I wanted everything defined in Infrastructure as Code (IaC) so I could spin environments up and down whenever I needed. That also aligns perfectly with what my team at Jamf is working on right now. We are making some changes to our underlying Azure architecture, including deeper network isolation, security controls, intergration with Jamf security cloud security products, and a shift from Bicep to OpenTofu. We will also be using AI agents to do a lot of the heavy lifting in that refactor. I will be sharing more about that in future blogs and talks as much as I am able to publicly.</p>



<p>Because OpenTofu is at the center of that work, I decided to build my entire Azure lab using OpenTofu and a full hub-and-spoke architecture. This gives my team a real, working reference base implementation that we can build on for production designs. I also want to share this with the larger tech community. <br><br>If you are note familiar with OpenTofu it is an open source infrastructure-as-code engine based on Terraform that lets you define, deploy, and manage cloud infrastructure using declarative configuration files, and you can learn more at <a href="https://opentofu.org" target="_blank" rel="noreferrer noopener">https://opentofu.org</a>.</p>



<p>You can access the GitHub Repository of my &#8220;<strong>OpenTofu Azure Hub and Spoke</strong>&#8221; solution here: <a href="https://github.com/Buchatech/OpenTofu-Azure-HubSpoke-public" target="_blank" rel="noreferrer noopener">https://github.com/Buchatech/OpenTofu-Azure-HubSpoke-public</a></p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="687" height="245" src="https://www.buchatech.com/wp-content/uploads/2026/01/image-2.png" alt="" class="wp-image-9508" style="width:480px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2026/01/image-2.png 687w, https://www.buchatech.com/wp-content/uploads/2026/01/image-2-300x107.png 300w" sizes="auto, (max-width: 687px) 100vw, 687px" /></figure>



<p>Lets break down whats in the solution I built. </p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Solution Architecture</h3>



<p>The solution deploys a production-style Azure network and platform foundation that includes:</p>



<ul class="wp-block-list">
<li><strong>Hub VNet</strong> with Azure Firewall, VPN Gateway, and DNS Private Resolver</li>



<li><strong>Spoke VNet</strong> with peering and default routes through the firewall</li>



<li><strong>Key Vault</strong> and <strong>Azure Container Registry</strong> using private endpoints</li>



<li><strong>Optional Jumpbox VM</strong> for secure management access</li>



<li><strong>GitHub Actions CI/CD pipeline</strong> using OIDC authentication</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">How the Automation Works</h3>



<p>This is a multi-part solution built around a bootstrap Bash script (bootstrap.sh) and a fully generated OpenTofu repository.</p>



<p>The bootstrap script creates everything you need to get started:</p>



<ol class="wp-block-list">
<li>It creates an Azure Storage Account to store your OpenTofu remote state.</li>



<li>It generates a complete OpenTofu project, including modules, variables, and environment structure.</li>



<li>It configures the backend so OpenTofu uses Azure Storage for state.</li>



<li>It creates a ready-to-use GitHub Actions pipeline for CI/CD.</li>
</ol>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="481" height="693" src="https://www.buchatech.com/wp-content/uploads/2026/01/image-3.png" alt="" class="wp-image-9510" style="width:434px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2026/01/image-3.png 481w, https://www.buchatech.com/wp-content/uploads/2026/01/image-3-208x300.png 208w" sizes="auto, (max-width: 481px) 100vw, 481px" /></figure>



<p>Once the repository is generated, you can deploy your Azure environment by running OpenTofu locally or by pushing the repo to GitHub and letting the pipeline handle deployments for you. Within minutes, you can have a fully functional Azure hub-and-spoke environment up and running, and you can customize the generated modules to fit your own requirements.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Deployment Modes</h3>



<p>The bootstrap bash script supports two deployment modes depending on how advanced and locked-down you want the environment to be.</p>



<p><strong>FULL Mode (Default)</strong><br>This is the enterprise-grade option.</p>



<ul class="wp-block-list">
<li>Hub VNet with Azure Firewall, VPN Gateway, and DNS Private Resolver</li>



<li>Spoke VNet with peering and default route through the firewall</li>



<li>Private endpoints for Key Vault and Azure Container Registry</li>



<li>Optional Jumpbox VM for secure management</li>



<li>GitHub Actions CI/CD pipeline with OIDC authentication</li>
</ul>



<p><strong>BASIC Mode</strong><br>This is a simpler version for learning or labs.</p>



<ul class="wp-block-list">
<li>Hub VNet with Azure Firewall only</li>



<li>Spoke VNet with peering and default route through the firewall</li>



<li>Public access for Key Vault and Azure Container Registry</li>



<li>No Jumpbox, VPN Gateway, or DNS Private Resolver</li>



<li>GitHub Actions CI/CD pipeline with OIDC authentication</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">What the <code>bootstrap.sh</code> Script Does</h3>



<p>When you run the bootstrap script, it will:</p>



<ol class="wp-block-list">
<li>Prompt you to select <strong>FULL</strong> or <strong>BASIC</strong> deployment mode</li>



<li>Create an Azure Storage Account for OpenTofu remote state in <code>rg-tfstate</code></li>



<li>Generate the full OpenTofu repository structure based on your choice</li>



<li>Configure the OpenTofu backend to use the storage account</li>



<li>Create GitHub Actions workflow files for CI/CD</li>



<li>Output the storage account details and the GitHub secrets you need to configure</li>
</ol>



<p>From there, you are ready to deploy and customize the script and OpenTofu based on your Azure hub-and-spoke environment entirely through code.<br><br>Here is the Readme from the repo. It goes even more in depth into my &#8220;<strong>OpenTofu Azure Hub and Spoke</strong>&#8221; solution. I hope you find it useful! </p>



<p><strong>********************************************************************************</strong></p>



<h1 class="wp-block-heading">Azure Hub-Spoke with OpenTofu<a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#azure-hub-spoke-with-opentofu"></a></h1>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Azure base network architecture solution</strong></p>
</blockquote>



<p>This repository contains a production-ready, modular OpenTofu configuration that deploys Azure hub-spoke network topology with&nbsp;<strong>two deployment modes (private or public)</strong>&nbsp;to match your requirements and budget.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Architecture Overview<a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#architecture-overview"></a></h2>



<h6 class="wp-block-heading">This solution deploys a&nbsp;<strong>hub-and-spoke network architecture</strong>&nbsp;(visual shows full-private deployment):<a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#this-solution-deploys-a-hub-and-spoke-network-architecture-visual-shows-full-private-deployment"></a></h6>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Enterprise-grade Azure network architecture lab environment with Site-to-Site VPN, Azure Firewall, DNS Private Resolver, and core services</strong></p>
</blockquote>



<p>This repository contains a production-ready, modular OpenTofu (Terraform) configuration that deploys a complete Azure hub-spoke network topology designed for hybrid cloud scenarios, connecting your on-premises network (e.g., UniFi network) to Azure.</p>



<h2 class="wp-block-heading">Architecture Overview<a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#architecture-overview-1"></a></h2>



<p>This lab deploys a&nbsp;<strong>hub-and-spoke network architecture</strong>&nbsp;following Azure best practices (visual shows full private deployment):</p>



<pre class="wp-block-code"><code>┌──────────────────────────────────────────────────────────────────────┐
│                            AZURE CLOUD                                │
│                                                                        │
│  ┌─── HUB VNet (rg-lab-hub-network) ────────────────────────┐        │
│  │ 10.10.0.0/16                                              │        │
│  │                                                            │        │
│  │  ┌──────────┐  ┌───────────┐  ┌────────────┐  ┌───────┐ │        │
│  │  │  Azure   │  │    VPN    │  │    DNS     │  │Jumpbox│ │        │
│  │  │ Firewall │  │  Gateway  │  │  Private   │  │  VM   │ │        │
│  │  │(10.10.1.0│  │(10.10.2.0)│  │  Resolver  │  │(Mgmt) │ │        │
│  │  │)+ DNAT   │  │           │  │(10.10.4-5.0│  │subnet │ │        │
│  │  │SSH:2222  │  │           │  │)           │  │       │ │        │
│  │  └─────┬────┘  └─────┬─────┘  └────────────┘  └───────┘ │        │
│  │        │             │                                     │        │
│  │        │             │  Site-to-Site VPN                  │        │
│  └────────┼─────────────┼─────────────────────────────────────┘        │
│           │             │                                               │
│           │  VNet Peering + Gateway Transit                            │
│           │             │                                               │
│  ┌────────▼─ SPOKE VNet (rg-lab-spoke1-network) ──────┐               │
│  │ 10.20.0.0/16                                        │               │
│  │                                                      │               │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐ │               │
│  │  │   Apps   │  │   APIs   │  │   Data/Services  │ │               │
│  │  │ Subnet   │  │ Subnet   │  │     Subnet       │ │               │
│  │  │          │  │          │  │  - ACR (Private) │ │               │
│  │  │          │  │          │  │  - Key Vault     │ │               │
│  │  └──────────┘  └──────────┘  └──────────────────┘ │               │
│  │                                                      │               │
│  │  Traffic routed through Azure Firewall ─────────────┘               │
│  └──────────────────────────────────────────────────────               │
│                                                                         │
│  ┌─── Management RG (rg-lab-management) ────────────┐                 │
│  │  - Azure Container Registry (ACR)                 │                 │
│  │  - Azure Key Vault                                 │                 │
│  │  - Private Endpoints in Spoke Data subnet         │                 │
│  └────────────────────────────────────────────────────┘                 │
│                                                                         │
└─────────────────────────────┬───────────────────────────────────────────┘
                              │
                      S2S VPN Tunnel (IPsec)
                              │
              ┌───────────────▼──────────────┐
              │   ON-PREMISES NETWORK        │
              │   (e.g., UniFi Router)       │
              │   192.168.1.0/24             │
              │                              │
              │   SSH → Azure Firewall:2222  │
              │   → DNAT → Jumpbox:22        │
              └──────────────────────────────┘
</code></pre>



<span id="more-9489"></span>



<h3 class="wp-block-heading">Key Components<a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#key-components"></a></h3>



<h4 class="wp-block-heading"><strong>Resource Organization</strong></h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#resource-organization"></a></p>



<p>Infrastructure is organized into&nbsp;<strong>three separate resource groups</strong>&nbsp;for better management and RBAC:</p>



<ol class="wp-block-list">
<li><strong>Hub Network RG</strong>&nbsp;(<code>rg-lab-hub-network</code>) &#8211; Core networking infrastructure</li>



<li><strong>Spoke Network RG</strong>&nbsp;(<code>rg-lab-spoke1-network</code>) &#8211; Application network</li>



<li><strong>Management RG</strong>&nbsp;(<code>rg-lab-management</code>) &#8211; Compute, services, and operational tools</li>
</ol>



<h4 class="wp-block-heading"><strong>Network Architecture</strong></h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#network-architecture"></a></p>



<ol class="wp-block-list">
<li><strong>Hub VNet (10.10.0.0/16)</strong>&nbsp;&#8211; Central connectivity point
<ul class="wp-block-list">
<li><strong>Azure Firewall</strong>&nbsp;for security and traffic inspection with DNAT rules</li>



<li><strong>VPN Gateway</strong>&nbsp;for site-to-site connectivity to on-premises</li>



<li><strong>DNS Private Resolver</strong>&nbsp;for hybrid DNS resolution</li>



<li><strong>Management subnet</strong>&nbsp;with Ubuntu jumpbox VM (static IP: 10.10.3.10)</li>
</ul>
</li>



<li><strong>Spoke VNet (10.20.0.0/16)</strong>&nbsp;&#8211; Application workload network
<ul class="wp-block-list">
<li><strong>Apps</strong>&nbsp;subnet (10.20.1.0/24) &#8211; Application tier</li>



<li><strong>APIs</strong>&nbsp;subnet (10.20.2.0/24) &#8211; API/microservices tier</li>



<li><strong>Data</strong>&nbsp;subnet (10.20.3.0/24) &#8211; Data tier and private endpoints</li>
</ul>
</li>
</ol>



<h4 class="wp-block-heading"><strong>Security &amp; Access</strong></h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#security--access"></a></p>



<ol start="3" class="wp-block-list">
<li><strong>Firewall-Based Security</strong>
<ul class="wp-block-list">
<li>Azure Firewall in the hub for centralized traffic inspection</li>



<li><strong>DNAT rules</strong>&nbsp;for secure jumpbox access (SSH port 2222 → 22)</li>



<li>Route tables forcing all spoke and hub management traffic through firewall</li>



<li>Configurable source IP restrictions for SSH access</li>



<li>Private endpoints for Azure services (no public access)</li>
</ul>
</li>



<li><strong>Jumpbox (Bastion Host)</strong>
<ul class="wp-block-list">
<li><strong>Ubuntu 22.04 LTS</strong>&nbsp;VM in hub management subnet</li>



<li><strong>Static private IP</strong>&nbsp;(10.10.3.10) for predictable firewall rules</li>



<li><strong>SSH-only authentication</strong>&nbsp;(no passwords)</li>



<li><strong>Accessible via firewall DNAT</strong>:&nbsp;<code>ssh -p 2222 user@&lt;firewall-public-ip&gt;</code></li>



<li>Used for managing Azure VMs and testing connectivity</li>
</ul>
</li>
</ol>



<h4 class="wp-block-heading"><strong>Hybrid Connectivity</strong></h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#hybrid-connectivity"></a></p>



<ol start="5" class="wp-block-list">
<li><strong>Site-to-Site VPN</strong>
<ul class="wp-block-list">
<li>IPsec VPN tunnel connecting to your on-premises network</li>



<li>Gateway transit enabled (spoke can use hub&#8217;s VPN Gateway)</li>



<li>DNS Private Resolver for seamless name resolution across environments</li>
</ul>
</li>
</ol>



<h4 class="wp-block-heading"><strong>Platform Services</strong></h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#platform-services"></a></p>



<ol start="6" class="wp-block-list">
<li><strong>Core Azure Services</strong>&nbsp;(in Management RG)
<ul class="wp-block-list">
<li><strong>Azure Container Registry (ACR)</strong>&nbsp;with private endpoint</li>



<li><strong>Azure Key Vault</strong>&nbsp;with private endpoint</li>



<li>Private DNS zones for service resolution</li>



<li>All services accessible from jumpbox and on-premises via VPN</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Prerequisites</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-prerequisites"></a></p>



<ul class="wp-block-list">
<li><strong>Azure Subscription</strong>&nbsp;with appropriate permissions</li>



<li><strong>Azure CLI</strong>&nbsp;installed and configured</li>



<li><strong>OpenTofu</strong>&nbsp;(or Terraform) installed (<a href="https://opentofu.org/docs/intro/install/">install guide</a>)</li>



<li><strong>On-Premises VPN Device</strong>&nbsp;configured (e.g., UniFi Security Gateway)</li>



<li><strong>Git</strong>&nbsp;for version control (Github Recommended) (<a href="https://github.com/">Github.com</a>)</li>



<li><strong>VS Code</strong>&nbsp;for version an IDE to work with and run code (<a href="https://code.visualstudio.com/">code.visualstudio.com</a>)</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Deployment Modes</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-deployment-modes"></a></p>



<p>The bootstrap script supports two deployment modes to match your requirements:</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> FULL (private) Mode (Option 1 &#8211; Default)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-full-private-mode-option-1---default"></a></p>



<p><strong>Best for:</strong>&nbsp;Enterprise production-ready deployments with complete network isolation</p>



<p><strong>Includes:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Hub-Spoke network architecture with Azure Firewall</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Private Endpoints</strong>&nbsp;for Key Vault and Container Registry</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Azure DNS Private Resolver</strong>&nbsp;with on-premises DNS forwarding</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Site-to-Site VPN Gateway</strong>&nbsp;for hybrid connectivity</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Optional Jumpbox</strong>&nbsp;(Linux VM) for secure access</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Private DNS zones for privatelink endpoints</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> VNet peering with custom routing through firewall</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> DNAT rules for jumpbox SSH access through firewall</li>
</ul>



<p><strong>Required Configuration Variables:</strong></p>



<ul class="wp-block-list">
<li><code>subscription_id</code>,&nbsp;<code>tenant_id</code></li>



<li><code>onprem_public_ip</code>&nbsp;&#8211; Your on-premises VPN endpoint public IP</li>



<li><code>vpn_shared_key</code>&nbsp;&#8211; Strong pre-shared key for VPN connection</li>



<li><code>onprem_dns_servers</code>&nbsp;&#8211; List of on-premises DNS server IPs</li>



<li><code>forward_domain_name</code>&nbsp;&#8211; Domain name to forward to on-prem DNS</li>



<li><code>acr_name</code>,&nbsp;<code>key_vault_name</code>&nbsp;(must be globally unique)</li>
</ul>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> BASIC (public) Mode (Option 2)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-basic-public-mode-option-2"></a></p>



<p><strong>Best for:</strong>&nbsp;Development, testing, learning, or cost-sensitive non-production environments</p>



<p><strong>Includes:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Hub-Spoke network architecture with Azure Firewall</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Public access</strong>&nbsp;for Key Vault and Container Registry</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> VNet peering with routing through firewall</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Simplified configuration (fewer variables)</li>
</ul>



<p><strong>Excludes:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> DNS Private Resolver</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Site-to-Site VPN Gateway</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Jumpbox VM</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Private endpoints and DNS zones</li>
</ul>



<p><strong>Required Configuration Variables:</strong></p>



<ul class="wp-block-list">
<li><code>subscription_id</code>,&nbsp;<code>tenant_id</code></li>



<li><code>acr_name</code>,&nbsp;<code>key_vault_name</code>&nbsp;(must be globally unique)</li>
</ul>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> How to Choose Your Deployment Mode</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-how-to-choose-your-deployment-mode"></a></p>



<p>When you run the bootstrap script, you&#8217;ll be prompted:</p>



<pre class="wp-block-preformatted">Select deployment type:
  1) Full (Private endpoints, DNS Resolver, VPN, optional Jumpbox) [default]
  2) Basic (Public access, no DNS/VPN/Jumpbox)

Enter choice [1-2]:</pre>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Module Comparison</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-module-comparison"></a></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Module</th><th>FULL Mode</th><th>BASIC Mode</th></tr></thead><tbody><tr><td><code>network-hub</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><code>network-spoke</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><code>peering-routing</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><code>security-firewall</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (with DNAT)</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (no DNAT)</td></tr><tr><td><code>services-core</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (private)</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (public)</td></tr><tr><td><code>dns-private-resolver</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><code>vpn-s2s</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td><code>compute-jumpbox</code></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (optional)</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3f7.png" alt="🏷" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Custom Prefix Feature</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#%EF%B8%8F-custom-prefix-feature"></a></p>



<p>The bootstrap script allows you to customize the naming of your resources with a unique prefix. This is particularly useful when:</p>



<ul class="wp-block-list">
<li><strong>Running multiple deployments</strong>&nbsp;in the same subscription</li>



<li><strong>Avoiding naming conflicts</strong>&nbsp;with existing resources</li>



<li><strong>Creating environment-specific deployments</strong>&nbsp;(dev, test, prod)</li>



<li><strong>Multiple users</strong>&nbsp;deploying from the same repository</li>
</ul>



<p><strong>How it works:</strong></p>



<p>When you run&nbsp;<code>bootstrap.sh</code>, you&#8217;ll be prompted to enter a custom prefix:</p>



<pre class="wp-block-preformatted">Enter prefix [default: lab]: myproject</pre>



<p><strong>Naming Convention:</strong></p>



<p>Your prefix will be used to create unique names for all resources:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Resource Type</th><th>Naming Pattern</th><th>Example (prefix: &#8220;myproject&#8221;)</th></tr></thead><tbody><tr><td>Repository Folder</td><td><code>{prefix}-azure-lab-opentofu</code></td><td><code>myproject-azure-lab-opentofu</code></td></tr><tr><td>Storage Account</td><td><code>tfstate{prefix}{timestamp}</code></td><td><code>tfstatemyproject123456</code></td></tr><tr><td>Resource Groups</td><td><code>rg-tfstate-{prefix}</code>&nbsp;<code>rg-{prefix}-hub-network</code></td><td><code>rg-tfstate-myproject</code>&nbsp;<code>rg-myproject-hub-network</code></td></tr></tbody></table></figure>



<p><strong>Validation Rules:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Must be lowercase alphanumeric only (a-z, 0-9)</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Length: 3-15 characters</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> No special characters, spaces, or uppercase letters</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Default value:&nbsp;<code>lab</code>&nbsp;(if you press Enter without typing)</li>
</ul>



<p><strong>Examples:</strong></p>



<pre class="wp-block-preformatted"># Development environment
Enter prefix [default: lab]: dev
# Creates: dev-azure-lab-opentofu/, tfstatedev123456, rg-tfstate-dev

# Production environment
Enter prefix [default: lab]: prod
# Creates: prod-azure-lab-opentofu/, tfstateprod123456, rg-tfstate-prod

# Personal testing
Enter prefix [default: lab]: jsmith
# Creates: jsmith-azure-lab-opentofu/, tfstatejsmith123456, rg-tfstate-jsmith

# Use default
Enter prefix [default: lab]: 
# Creates: lab-azure-lab-opentofu/, tfstatelab123456, rg-tfstate-lab</pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Tip</strong>: Choose a prefix that clearly identifies the purpose or owner of the deployment. This makes it easier to manage multiple deployments and track resources in Azure.</p>
</blockquote>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Switching Between Modes</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-switching-between-modes"></a></p>



<h4 class="wp-block-heading">Upgrading from BASIC to FULL</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#upgrading-from-basic-to-full"></a></p>



<p>Upgrading requires re-running the bootstrap script and redeploying:</p>



<pre class="wp-block-preformatted"># 1. Destroy current BASIC deployment
cd envs/lab
tofu destroy

# 2. Re-run bootstrap with FULL mode
cd ../../bootstrap
./bootstrap.sh

# Select FULL mode and enter your prefix (same as before or new)

# 3. Configure terraform.tfvars with FULL mode variables
# Add: onprem_public_ip, vpn_shared_key, onprem_dns_servers, forward_domain_name

# 4. Navigate to the new repository (replace 'lab' with your prefix)
cd ../lab-azure-lab-opentofu/envs/lab

# 5. Deploy FULL infrastructure
tofu init
tofu apply</pre>



<h4 class="wp-block-heading">Downgrading from FULL to BASIC</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#downgrading-from-full-to-basic"></a></p>



<p>Not recommended as it may leave resources orphaned. Better to destroy and redeploy:</p>



<pre class="wp-block-preformatted"># 1. Destroy FULL deployment completely (replace 'lab' with your prefix)
cd lab-azure-lab-opentofu/envs/lab
tofu destroy

# 2. Delete the generated repository
cd ../../..
rm -rf lab-azure-lab-opentofu

# 3. Re-run bootstrap with BASIC mode
cd bootstrap
./bootstrap.sh

# Select BASIC mode and enter your prefix (same as before or new)

# 4. Navigate to the new repository (replace 'lab' with your prefix)
cd ../lab-azure-lab-opentofu

# 5. Configure simplified terraform.tfvars (no VPN/DNS variables)

# 6. Deploy BASIC infrastructure
cd envs/lab
tofu init
tofu apply</pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Bootstrap Script Quick Start</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-bootstrap-script-quick-start"></a></p>



<h3 class="wp-block-heading">Step 1: Authenticate to Azure</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-1-authenticate-to-azure"></a></p>



<pre class="wp-block-preformatted"># Login to Azure
--scope https://graph.microsoft.com/.default

# List available subscriptions
az account list -o table

# Set the subscription you want to use
az account set --subscription "&lt;your-subscription-id-or-name&gt;"

# Verify the active subscription and copy subscription ID and tenant ID for later use
SUBSCRIPTION_NAME=$(az account show --query name -o tsv)
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
TENANT_ID=$(az account show --query tenantId -o tsv)

echo Azure Subscription name = $SUBSCRIPTION_NAME
echo Subscription ID = $SUBSCRIPTION_ID
echo Tenant ID = $TENANT_ID</pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Tip</strong>: It is recommended to run the Azure login and bootstrap script locally from VS code. After running it then push everything up to your GitHub repository. There will be instructions later on how to push up to the repository.</p>
</blockquote>



<h3 class="wp-block-heading">Step 2: Generate Repository Structure with Remote State</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-2-generate-repository-structure-with-remote-state"></a></p>



<p>The&nbsp;<code>bootstrap.sh</code>&nbsp;script scaffolds the entire project structure and sets up Azure Storage for remote state management:</p>



<pre class="wp-block-preformatted"># Run the bootstrap script
bash ./bootstrap.sh</pre>



<p>When prompted, you&#8217;ll be asked to:</p>



<ol class="wp-block-list">
<li><strong>Choose deployment mode</strong>: Enter&nbsp;<code>BASIC</code>&nbsp;or&nbsp;<code>FULL</code>&nbsp;(or press Enter for FULL)</li>



<li><strong>Enter a custom prefix</strong>: Enter a unique prefix (3-15 lowercase alphanumeric characters) or press Enter for default&nbsp;<code>lab</code></li>
</ol>



<p>Example interaction:</p>



<pre class="wp-block-code"><code>Enter deployment type &#91;BASIC/FULL, default: FULL]: FULL
Enter prefix &#91;default: lab]: myproject
</code></pre>



<p>This will create resources with your custom prefix:</p>



<ul class="wp-block-list">
<li><strong>Repository folder</strong>:&nbsp;<code>myproject-azure-lab-opentofu/</code></li>



<li><strong>Storage Account</strong>:&nbsp;<code>tfstatemyproject123456</code>&nbsp;(prefix + timestamp)</li>



<li><strong>Resource Group</strong>:&nbsp;<code>rg-tfstate-myproject</code></li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Tip</strong>: A companion file&nbsp;<code>bootstrap-steps.sh</code>&nbsp;is provided as a step-by-step walkthrough guide. This helper file contains detailed instructions, examples, and troubleshooting tips for running the bootstrap script and working with the infrastructure. Reference it if you need additional context or guidance during deployment.</p>
</blockquote>



<p><strong>What gets created:</strong></p>



<ul class="wp-block-list">
<li><strong>Azure Storage Account</strong>&nbsp;for OpenTofu state with your custom prefix</li>



<li>Complete folder structure with 9 modular components (FULL) or 4 modules (BASIC)</li>



<li>Starter OpenTofu configuration files with&nbsp;<strong>backend configured</strong></li>



<li>GitHub Actions workflow for CI/CD</li>



<li><code>.gitignore</code>&nbsp;for OpenTofu files</li>



<li>Git repository with initial commit</li>
</ul>



<p><strong>Important</strong>: The script will output the storage account name (e.g.,&nbsp;<code>tfstatemyproject123456</code>). Save this information &#8211; you&#8217;ll need it for GitHub Actions setup.</p>



<h3 class="wp-block-heading">Why Remote State?</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#why-remote-state"></a></p>



<p>Remote state storage in Azure ensures:</p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>State persistence</strong>&nbsp;between GitHub Actions runs</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>State locking</strong>&nbsp;to prevent concurrent modifications</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Team collaboration</strong>&nbsp;&#8211; multiple users can work on the same infrastructure</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Secure storage</strong>&nbsp;with encryption and RBAC</li>
</ul>



<p>Without remote state, GitHub Actions would lose track of your infrastructure after each run, causing &#8220;resource already exists&#8221; errors.</p>



<h3 class="wp-block-heading">Step 3: Configure Your Environment</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-3-configure-your-environment"></a></p>



<p>Navigate to the lab environment (replace&nbsp;<code>lab</code>&nbsp;with your custom prefix):</p>



<pre class="wp-block-preformatted">cd lab-azure-lab-opentofu/envs/lab</pre>



<p><strong>Or if you used a custom prefix like &#8220;myproject&#8221;:</strong></p>



<pre class="wp-block-preformatted">cd myproject-azure-lab-opentofu/envs/lab</pre>



<p>Edit&nbsp;<code>terraform.tfvars</code>&nbsp;with your specific values.</p>



<p><strong>The required variables depend on your chosen deployment mode:</strong></p>



<h4 class="wp-block-heading">For FULL Mode (Default):</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#for-full-mode-default"></a></p>



<p>======= Edit&nbsp;<code>terraform.tfvars</code>&nbsp;with your specific values:</p>



<pre class="wp-block-preformatted">subscription_id = "YOUR_SUBSCRIPTION_GUID"    # az account show --query id -o tsv
tenant_id       = "YOUR_TENANT_GUID"          # az account show --query tenantId -o tsv

# Your on-premises network details
onprem_public_ip      = "X.X.X.X"            # Your public WAN IP
onprem_address_spaces = ["192.168.1.0/24"]   # Your home/office network
vpn_shared_key        = "YOUR_SECURE_KEY"    # Must match your VPN device

# DNS Private Resolver settings
onprem_dns_servers  = ["192.168.1.1"]        # Your on-prem DNS server
forward_domain_name = "home.arpa."            # Your local domain

=======
# Must be globally unique and lowercase
acr_name       = "acrlabunique12345"
key_vault_name = "kvlabunique12345"

# Jumpbox configuration (optional - set enable_jumpbox = true to deploy)
enable_jumpbox             = false            # Toggle jumpbox deployment
jumpbox_private_ip         = "10.10.3.10"     # Static IP in management subnet
jumpbox_vm_size            = "Standard_DS1_v2" # VM size (reliable availability)
jumpbox_admin_username     = "azureuser"      # SSH username
jumpbox_ssh_public_key     = "ssh-ed25519 AAAA..."  # Your SSH public key

# Jumpbox firewall access (only applies when enable_jumpbox = true)
enable_jumpbox_dnat        = true             # Enable DNAT rule for SSH
jumpbox_inbound_port       = 2222             # External SSH port on firewall
jumpbox_allowed_ssh_source = ["0.0.0.0/0"]   # Source IPs for SSH (recommend your IP /32)
</pre>



<h4 class="wp-block-heading">For BASIC Mode:</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#for-basic-mode"></a></p>



<pre class="wp-block-preformatted">subscription_id = "YOUR_SUBSCRIPTION_GUID"    # az account show --query id -o tsv
tenant_id       = "YOUR_TENANT_GUID"          # az account show --query tenantId -o tsv

# Must be globally unique and lowercase
acr_name       = "acrlabunique12345"
key_vault_name = "kvlabunique12345"

# Network CIDRs (optional - defaults provided)
hub_vnet_cidr   = ["10.0.0.0/16"]
spoke_vnet_cidr = ["10.1.0.0/16"]
=======

# Optional: Customize DNS settings
onprem_dns_servers  = ["192.168.1.1"]        # Your on-prem DNS server
forward_domain_name = "home.arpa."            # Your local domain</pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Important</strong>: Replace the following placeholders:</p>



<ul class="wp-block-list">
<li><code>YOUR_SUBSCRIPTION_GUID</code>&nbsp;and&nbsp;<code>YOUR_TENANT_GUID</code>&nbsp;&#8211; Get from&nbsp;<code>az account show</code></li>



<li>(FULL mode)&nbsp;<code>X.X.X.X</code>&nbsp;&#8211; Your on-premises public IP and your workstation IP for SSH</li>



<li>(FULL mode)&nbsp;<code>YOUR_SECURE_KEY</code>&nbsp;&#8211; Strong VPN shared key (must match UniFi configuration)</li>



<li>(FULL mode)&nbsp;<code>ssh-ed25519 AAAA...</code>&nbsp;&#8211; Your actual SSH public key from&nbsp;<code>~/.ssh/id_ed25519.pub</code>&nbsp;or&nbsp;<code>id_rsa.pub</code></li>



<li><code>acrlabunique12345</code>&nbsp;and&nbsp;<code>kvlabunique12345</code>&nbsp;&#8211; Globally unique names</li>
</ul>
</blockquote>



<h3 class="wp-block-heading">Step 4: Deploy the Infrastructure</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-4-deploy-the-infrastructure"></a></p>



<pre class="wp-block-preformatted"># Format code
tofu fmt -recursive

# Initialize OpenTofu
tofu init -upgrade

# Validate configuration
tofu validate

# Review planned changes
tofu plan

# Deploy (confirm when prompted)
tofu apply</pre>



<p><strong>Note:</strong>&nbsp;If you selected full mode the deployment takes approximately&nbsp;<strong>30-45 minutes</strong>&nbsp;due to VPN Gateway provisioning.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c1.png" alt="📁" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Repository Structure</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-repository-structure"></a></p>



<pre class="wp-block-code"><code>azure-lab-opentofu/
├── envs/
│   └── lab/                          # Lab environment configuration
│       ├── main.tf                   # Main orchestration file
│       ├── variables.tf              # Input variable definitions
│       ├── terraform.tfvars          # Variable values (customize this!)
│       └── outputs.tf                # Output values
│
├── modules/                          # Reusable infrastructure modules
│   ├── network-hub/                  # Hub VNet with specialized subnets
│   │   ├── main.tf                   # Hub VNet, subnets (Firewall, Gateway, DNS, Mgmt)
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── network-spoke/                # Spoke VNet for workloads
│   │   ├── main.tf                   # Spoke VNet with Apps/APIs/Data subnets
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── compute-jumpbox/              # Ubuntu jumpbox VM
│   │   ├── main.tf                   # Linux VM with static IP, SSH key auth
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── security-firewall/            # Azure Firewall with DNAT
│   │   ├── main.tf                   # Firewall with public IP, DNAT rules
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── hub-routing/                  # Hub subnet routing
│   │   ├── main.tf                   # Route table for management subnet
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── vpn-s2s/                      # Site-to-Site VPN
│   │   ├── main.tf                   # VPN Gateway, Local Gateway, Connection
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── peering-routing/              # VNet peering and spoke routing
│   │   ├── main.tf                   # Hub-spoke peering, spoke route tables
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   ├── dns-private-resolver/         # DNS Private Resolver
│   │   ├── main.tf                   # Resolver with inbound/outbound endpoints
│   │   ├── variables.tf
│   │   └── outputs.tf
│   │
│   └── services-core/                # Core Azure services
│       ├── main.tf                   # ACR, Key Vault with private endpoints
│       ├── variables.tf
│       └── outputs.tf
│
├── .github/
│   └── workflows/
│       └── tofu.yml                  # CI/CD pipeline for OpenTofu
│
└── .gitignore                        # Ignore Terraform state and lock files
</code></pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Module Breakdown</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-module-breakdown"></a></p>



<h3 class="wp-block-heading">1. Network Hub (<code>modules/network-hub</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#1-network-hub-modulesnetwork-hub"></a></p>



<p><strong>Purpose</strong>: Creates the central hub VNet that serves as the connectivity point for all resources.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates Hub VNet (10.10.0.0/16)</li>



<li><strong>AzureFirewallSubnet</strong>&nbsp;(10.10.1.0/26) &#8211; Reserved for Azure Firewall</li>



<li><strong>GatewaySubnet</strong>&nbsp;(10.10.2.0/27) &#8211; Reserved for VPN Gateway</li>



<li><strong>Management</strong>&nbsp;subnet (10.10.3.0/24) &#8211; For jump boxes, bastion hosts</li>



<li><strong>DnsInbound</strong>&nbsp;subnet (10.10.4.0/28) &#8211; For DNS Private Resolver inbound endpoint</li>



<li><strong>DnsOutbound</strong>&nbsp;subnet (10.10.5.0/28) &#8211; For DNS Private Resolver outbound endpoint</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Subnet delegations for DNS Private Resolver endpoints</li>



<li>Outputs VNet ID, subnet IDs for other modules to consume</li>
</ul>



<h3 class="wp-block-heading">2. Network Spoke (<code>modules/network-spoke</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#2-network-spoke-modulesnetwork-spoke"></a></p>



<p><strong>Purpose</strong>: Creates the spoke VNet for application workloads.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates Spoke VNet (10.20.0.0/16)</li>



<li><strong>Apps</strong>&nbsp;subnet (10.20.1.0/24) &#8211; Application tier</li>



<li><strong>APIs</strong>&nbsp;subnet (10.20.2.0/24) &#8211; API/microservices tier</li>



<li><strong>Data</strong>&nbsp;subnet (10.20.3.0/24) &#8211; Data tier and private endpoints</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Private endpoint network policies disabled on all subnets</li>



<li>Structured for multi-tier application deployment</li>
</ul>



<h3 class="wp-block-heading">3. Compute Jumpbox (<code>modules/compute-jumpbox</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#3-compute-jumpbox-modulescompute-jumpbox"></a></p>



<p><strong>Purpose</strong>: Deploys a secure Ubuntu Linux jumpbox/bastion host for managing Azure resources.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates a network interface with&nbsp;<strong>static private IP</strong>&nbsp;(10.10.3.10)</li>



<li>Deploys Ubuntu 22.04 LTS virtual machine</li>



<li>Configures SSH-only authentication (no passwords)</li>



<li>Uses provided SSH public key for access</li>



<li>Placed in hub management subnet</li>



<li><strong>Optional deployment</strong>&nbsp;&#8211; controlled by&nbsp;<code>enable_jumpbox</code>&nbsp;variable</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li><strong>Fully optional</strong>&nbsp;&#8211; Set&nbsp;<code>enable_jumpbox = false</code>&nbsp;to skip deployment entirely</li>



<li><strong>Static IP</strong>&nbsp;ensures firewall DNAT rules are stable and predictable</li>



<li>Standard_DS1_v2 VM size (reliable availability in most Azure regions)</li>



<li><strong>SSH key authentication only</strong>&nbsp;&#8211; more secure than passwords</li>



<li>Used for accessing Azure VMs, testing connectivity, and administrative tasks</li>



<li>Can reach on-premises via VPN and all Azure services via private endpoints</li>
</ul>



<p><strong>Typical uses</strong>:</p>



<ul class="wp-block-list">
<li>SSH gateway to other Azure VMs</li>



<li>Testing network connectivity and DNS resolution</li>



<li>Running Azure CLI commands in a cloud environment</li>



<li>Accessing private endpoint services (ACR, Key Vault)</li>
</ul>



<h3 class="wp-block-heading">4. Security Firewall (<code>modules/security-firewall</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#4-security-firewall-modulessecurity-firewall"></a></p>



<p><strong>Purpose</strong>: Deploys Azure Firewall for centralized network security with optional DNAT rules for jumpbox access.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates a Standard SKU public IP</li>



<li>Deploys Azure Firewall (Standard tier)</li>



<li>Attaches firewall to the hub&#8217;s AzureFirewallSubnet</li>



<li><strong>Conditionally creates DNAT rule</strong>&nbsp;to forward external SSH (port 2222) to jumpbox (port 22)</li>



<li><strong>Conditionally creates network allow rule</strong>&nbsp;for SSH traffic to jumpbox</li>



<li>Configurable source IP restrictions for SSH access</li>



<li>DNAT rules only created when&nbsp;<strong>both</strong>&nbsp;<code>enable_jumpbox = true</code>&nbsp;AND&nbsp;<code>enable_jumpbox_dnat = true</code></li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Centralized egress and inspection point</li>



<li><strong>DNAT (Destination NAT)</strong>&nbsp;translates&nbsp;<code>firewall-ip:2222</code>&nbsp;→&nbsp;<code>jumpbox:22</code>&nbsp;(when enabled)</li>



<li>Fully optional jumpbox integration &#8211; firewall works standalone without jumpbox</li>



<li>Outputs private IP (used by route tables) and public IP</li>



<li>Secure jumpbox access without exposing VM directly to internet</li>
</ul>



<p><strong>Security model</strong>&nbsp;(when jumpbox enabled):</p>



<pre class="wp-block-code"><code>Internet → Firewall Public IP:2222 → DNAT → Jumpbox 10.10.3.10:22
             (Restricted by source IP)
</code></pre>



<h3 class="wp-block-heading">5. Hub Routing (<code>modules/hub-routing</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#5-hub-routing-moduleshub-routing"></a></p>



<p><strong>Purpose</strong>: Forces hub management subnet traffic through the firewall for security inspection.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates route table for hub management subnet</li>



<li>Adds default route (0.0.0.0/0) pointing to firewall private IP</li>



<li>Associates route table with management subnet</li>



<li>Ensures jumpbox egress traffic is inspected by firewall</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Consistent security policy &#8211; all hub traffic (like spoke) goes through firewall</li>



<li>Jumpbox internet access is controlled and monitored</li>



<li>Enables firewall logging and inspection of management traffic</li>
</ul>



<h3 class="wp-block-heading">6. VPN Site-to-Site (<code>modules/vpn-s2s</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#6-vpn-site-to-site-modulesvpn-s2s"></a></p>



<p><strong>Purpose</strong>: Deploys Azure Firewall for centralized network security and traffic inspection.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates a Standard SKU public IP</li>



<li>Deploys Azure Firewall (Standard tier)</li>



<li>Attaches firewall to the hub&#8217;s AzureFirewallSubnet</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Centralized egress and inspection point</li>



<li>Outputs private IP (used by route tables) and public IP</li>
</ul>



<h3 class="wp-block-heading">4. VPN Site-to-Site (<code>modules/vpn-s2s</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#4-vpn-site-to-site-modulesvpn-s2s"></a></p>



<p><strong>Purpose</strong>: Establishes secure IPsec VPN connection to your on-premises network.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates a dynamic public IP for the VPN Gateway</li>



<li>Deploys Azure VPN Gateway (configurable SKU, default VpnGw1)</li>



<li>Creates Local Network Gateway (represents your on-premises VPN device)</li>



<li>Establishes VPN connection with shared key</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Route-based VPN type for flexibility</li>



<li>IPsec tunnel encryption</li>



<li>Connects Azure VNet to on-premises (e.g., UniFi router)</li>
</ul>



<p><strong>On-premises requirements</strong>:</p>



<ul class="wp-block-list">
<li>Your router&#8217;s public IP</li>



<li>VPN shared key (must match on both sides)</li>



<li>IPsec/IKEv2 support</li>
</ul>



<h3 class="wp-block-heading">5. Peering &amp; Routing (<code>modules/peering-routing</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#5-peering--routing-modulespeering-routing"></a></p>



<p><strong>Purpose</strong>: Connects hub and spoke VNets and forces traffic through the firewall.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates bidirectional VNet peering between hub and spoke</li>



<li>Hub allows gateway transit (spoke can use VPN Gateway)</li>



<li>Spoke uses remote gateways (for VPN connectivity)</li>



<li>Creates route table with default route (0.0.0.0/0) to Azure Firewall</li>



<li>Associates route table with all spoke subnets</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Forces all spoke traffic through the firewall for inspection</li>



<li>Enables spoke to communicate with on-premises via hub&#8217;s VPN Gateway</li>
</ul>



<h3 class="wp-block-heading">6. DNS Private Resolver (<code>modules/dns-private-resolver</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#6-dns-private-resolver-modulesdns-private-resolver"></a></p>



<p><strong>Purpose</strong>: Enables hybrid DNS resolution between Azure and on-premises.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates Azure DNS Private Resolver in the hub</li>



<li><strong>Inbound endpoint</strong>&nbsp;&#8211; Allows on-premises to query Azure DNS</li>



<li><strong>Outbound endpoint</strong>&nbsp;&#8211; Allows Azure to forward queries to on-premises DNS</li>



<li>Creates forwarding ruleset for your on-premises domain (e.g.,&nbsp;<code>home.arpa.</code>)</li>



<li>Links ruleset to hub and spoke VNets</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>Seamless DNS resolution across hybrid environments</li>



<li>Conditional forwarding to on-premises DNS server</li>



<li>Resolves private endpoint DNS names automatically</li>
</ul>



<p><strong>How it works</strong>:</p>



<ul class="wp-block-list">
<li>Azure VMs can resolve on-premises hostnames (e.g.,&nbsp;<code>server.home.arpa</code>)</li>



<li>On-premises devices can resolve Azure private endpoints</li>



<li>No need for custom DNS servers in VNets</li>
</ul>



<h3 class="wp-block-heading">7. Services Core (<code>modules/services-core</code>)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#7-services-core-modulesservices-core"></a></p>



<p><strong>Purpose</strong>: Deploys core Azure PaaS services with private connectivity.</p>



<p><strong>What it does</strong>:</p>



<ul class="wp-block-list">
<li>Creates&nbsp;<strong>Azure Container Registry</strong>&nbsp;(ACR) &#8211; Standard SKU, no public access</li>



<li>Creates&nbsp;<strong>Azure Key Vault</strong>&nbsp;&#8211; Standard SKU, no public access, soft delete enabled</li>



<li>Creates private DNS zones:
<ul class="wp-block-list">
<li><code>privatelink.azurecr.io</code>&nbsp;&#8211; For ACR</li>



<li><code>privatelink.vaultcore.azure.net</code>&nbsp;&#8211; For Key Vault</li>
</ul>
</li>



<li>Links private DNS zones to hub and spoke VNets</li>



<li>Creates private endpoints in spoke&#8217;s Data subnet</li>



<li>Integrates private endpoints with private DNS zones</li>
</ul>



<p><strong>Key features</strong>:</p>



<ul class="wp-block-list">
<li>No public internet access to ACR or Key Vault</li>



<li>Automatic DNS resolution via private endpoints</li>



<li>Accessible from Azure VMs and on-premises (via VPN)</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Security Features</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-security-features"></a></p>



<h3 class="wp-block-heading">Network Security</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#network-security"></a></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Azure Firewall inspects all egress traffic from spoke</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Route tables force traffic through firewall (both spoke and hub management subnet)</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> No direct internet access from spoke subnets</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Jumpbox access</strong>&nbsp;via DNAT only &#8211; not directly exposed to internet</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Source IP restrictions</strong>&nbsp;can be configured for SSH access (firewall DNAT rule)</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Port translation</strong>&nbsp;(external 2222 → internal 22) obscures SSH service</li>
</ul>



<h3 class="wp-block-heading">Jumpbox Security</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#jumpbox-security"></a></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Fully optional deployment</strong>&nbsp;&#8211; Set&nbsp;<code>enable_jumpbox = false</code>&nbsp;to skip entirely</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>SSH key-only authentication</strong>&nbsp;&#8211; password auth disabled</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Static private IP</strong>&nbsp;(10.10.3.10) &#8211; no public IP assigned</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Firewall DNAT</strong>&nbsp;provides controlled external access (optional)</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Dual-layer control</strong>&nbsp;&#8211; DNAT only active when&nbsp;<code>enable_jumpbox = true</code>&nbsp;AND&nbsp;<code>enable_jumpbox_dnat = true</code></li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Hub routing</strong>&nbsp;forces jumpbox egress through firewall for inspection</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Management subnet isolation</strong>&nbsp;&#8211; separate from application workloads</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>VM size flexibility</strong>&nbsp;&#8211; Standard_DS1_v2 default, easily changed if SKU unavailable</li>
</ul>



<h3 class="wp-block-heading">Service Security</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#service-security"></a></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ACR and Key Vault have public access disabled</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Private endpoints used for all PaaS services</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Private DNS zones for secure name resolution</li>
</ul>



<h3 class="wp-block-heading">Secrets Management</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#secrets-management"></a></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> VPN shared key marked as sensitive in variables</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Key Vault ready for application secrets</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<code>.gitignore</code>&nbsp;prevents committing state files</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4e4.png" alt="📤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Outputs</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-outputs"></a></p>



<p>After deployment, OpenTofu outputs key information:</p>



<pre class="wp-block-preformatted">tofu output</pre>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Output</th><th>Description</th><th>Use Case</th></tr></thead><tbody><tr><td><code>firewall_public_ip</code></td><td>Azure Firewall&#8217;s public IP</td><td>Monitoring, firewall rules,&nbsp;<strong>SSH to jumpbox (if enabled)</strong></td></tr><tr><td><code>firewall_private_ip</code></td><td>Firewall&#8217;s internal IP</td><td>Routing configuration</td></tr><tr><td><code>vpn_gateway_public_ip</code></td><td>VPN Gateway&#8217;s public IP</td><td><strong>Configure in your on-premises VPN device</strong></td></tr><tr><td><code>jumpbox_private_ip</code></td><td>Jumpbox internal IP</td><td>10.10.3.10 (static) &#8211;&nbsp;<code>null</code>&nbsp;if disabled</td></tr><tr><td><code>jumpbox_ssh_via_firewall</code></td><td>Complete SSH command</td><td><strong>Connect to jumpbox via DNAT</strong>&nbsp;&#8211;&nbsp;<code>null</code>&nbsp;if disabled</td></tr><tr><td><code>jumpbox_status</code></td><td>Deployment status message</td><td>Helpful guidance for SKU capacity issues</td></tr><tr><td><code>dns_inbound_endpoint_ip</code></td><td>DNS resolver inbound IP</td><td>Configure on-prem DNS to forward to Azure</td></tr><tr><td><code>acr_login_server</code></td><td>Container registry URL</td><td>Docker login, image push/pull</td></tr><tr><td><code>key_vault_uri</code></td><td>Key Vault URL</td><td>Secret management, app configuration</td></tr></tbody></table></figure>



<p><strong>Example jumpbox SSH access</strong>&nbsp;(when&nbsp;<code>enable_jumpbox = true</code>):</p>



<pre class="wp-block-preformatted"># Get the SSH command
tofu output jumpbox_ssh_via_firewall

# Example output:
# ssh -p 2222 azureuser@&lt;firewall-public-ip&gt;

# This DNAT rule translates:
# firewall-public-ip:2222 → jumpbox 10.10.3.10:22</pre>



<p><strong>Note</strong>: If jumpbox is disabled (<code>enable_jumpbox = false</code>), jumpbox-related outputs will return&nbsp;<code>null</code>.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> On-Premises VPN Configuration (UniFi Example)</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-on-premises-vpn-configuration-unifi-example"></a></p>



<p>After deployment you can now configure your on-premises VPN device. Note that your on-premises VPN will differ based on the VPN solution you have. Confguration steps using the Unifi example are as follows:</p>



<h3 class="wp-block-heading">Get the Azure VPN Gateway IP</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#get-the-azure-vpn-gateway-ip"></a></p>



<pre class="wp-block-preformatted">cd azure-lab-opentofu/envs/lab
tofu output vpn_gateway_public_ip</pre>



<h3 class="wp-block-heading">UniFi Settings (Settings → VPN → Site-to-Site VPN)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#unifi-settings-settings--vpn--site-to-site-vpn"></a></p>



<ol class="wp-block-list">
<li><strong>Remote Gateway</strong>&nbsp;= Azure VPN Gateway Public IP (from output above)</li>



<li><strong>Pre-Shared Key</strong>&nbsp;= Same key you used in&nbsp;<code>terraform.tfvars</code></li>



<li><strong>Local Network</strong>&nbsp;= Your home network (e.g.,&nbsp;<code>192.168.1.0/24</code>)</li>



<li><strong>Remote Networks</strong>&nbsp;= Azure VNets:
<ul class="wp-block-list">
<li>Hub:&nbsp;<code>10.10.0.0/16</code></li>



<li>Spoke:&nbsp;<code>10.20.0.0/16</code></li>
</ul>
</li>
</ol>



<h3 class="wp-block-heading">Verify Connection</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#verify-connection"></a></p>



<p>From Azure Portal:</p>



<ol class="wp-block-list">
<li>Navigate to&nbsp;<strong>Virtual Network Gateways</strong></li>



<li>Select your gateway (<code>lab-vpngw</code>)</li>



<li>Go to&nbsp;<strong>Connections</strong></li>



<li>Status should show&nbsp;<strong>Connected</strong></li>
</ol>



<p>Test connectivity:</p>



<pre class="wp-block-preformatted"># From on-premises, ping an Azure VM
ping 10.20.1.10  # Example Azure VM IP

# From Azure VM, ping on-premises
ping 192.168.1.100  # Example on-prem device</pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Azure Point-to-Site VPN Client Setup</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-azure-point-to-site-vpn-client-setup"></a></p>



<p>If you need to connect to the Azure environment from your laptop when not on the corporate network, you can set up Azure Point-to-Site (P2S) VPN. This allows direct VPN connection from individual devices to Azure.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Note</strong>: The current OpenTofu configuration deploys&nbsp;<strong>Site-to-Site VPN</strong>&nbsp;only. To enable Point-to-Site VPN, you&#8217;ll need to add P2S configuration to the VPN Gateway.</p>
</blockquote>



<h3 class="wp-block-heading">Prerequisites</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#prerequisites"></a></p>



<p>Before setting up P2S VPN, you need to:</p>



<ol class="wp-block-list">
<li>Configure P2S on your VPN Gateway (requires additional OpenTofu configuration)</li>



<li>Generate client certificates (for certificate-based auth) OR configure Azure AD authentication</li>
</ol>



<h3 class="wp-block-heading">Option 1: Add P2S Configuration to VPN Gateway (Certificate Auth)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#option-1-add-p2s-configuration-to-vpn-gateway-certificate-auth"></a></p>



<p><strong>Step 1: Generate Root and Client Certificates</strong></p>



<p>On Windows (PowerShell as Administrator):</p>



<pre class="wp-block-preformatted"># Generate root certificate
$cert = New-SelfSignedCertificate `
  -Type Custom `
  -KeySpec Signature `
  -Subject "CN=AzureVPNRootCert" `
  -KeyExportPolicy Exportable `
  -HashAlgorithm sha256 `
  -KeyLength 2048 `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -KeyUsageProperty Sign `
  -KeyUsage CertSign

# Generate client certificate from root
New-SelfSignedCertificate `
  -Type Custom `
  -DnsName "AzureVPNClientCert" `
  -KeySpec Signature `
  -Subject "CN=AzureVPNClientCert" `
  -KeyExportPolicy Exportable `
  -HashAlgorithm sha256 `
  -KeyLength 2048 `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -Signer $cert `
  -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")

# Export root certificate public key (upload this to Azure)
$rootCertBase64 = [Convert]::ToBase64String($cert.RawData)
Write-Output $rootCertBase64 | Out-File -FilePath "$env:USERPROFILE\Desktop\AzureVPNRootCert.txt"</pre>



<p>On macOS/Linux:</p>



<pre class="wp-block-preformatted"># Generate root certificate and key
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout azure-vpn-root.key \
  -out azure-vpn-root.crt \
  -days 3650 \
  -subj "/CN=AzureVPNRootCert"

# Generate client certificate and key
openssl req -newkey rsa:2048 -nodes \
  -keyout azure-vpn-client.key \
  -out azure-vpn-client.csr \
  -subj "/CN=AzureVPNClientCert"

# Sign client certificate with root
openssl x509 -req -in azure-vpn-client.csr \
  -CA azure-vpn-root.crt \
  -CAkey azure-vpn-root.key \
  -CAcreateserial \
  -out azure-vpn-client.crt \
  -days 3650 \
  -extfile &lt;(echo "extendedKeyUsage=clientAuth")

# Get base64 encoded root cert (upload this to Azure)
cat azure-vpn-root.crt | grep -v "CERTIFICATE" | tr -d '\n' &gt; azure-vpn-root-base64.txt
cat azure-vpn-root-base64.txt</pre>



<p><strong>Step 2: Configure P2S on VPN Gateway via Azure Portal</strong></p>



<ol class="wp-block-list">
<li>Go to&nbsp;<strong>Azure Portal</strong>&nbsp;→&nbsp;<strong>Virtual network gateways</strong>&nbsp;→ Your gateway (<code>lab-vpngw</code>)</li>



<li>Click&nbsp;<strong>Point-to-site configuration</strong></li>



<li>Click&nbsp;<strong>Configure now</strong></li>



<li>Configure the following:
<ul class="wp-block-list">
<li><strong>Address pool</strong>:&nbsp;<code>172.16.0.0/24</code>&nbsp;(VPN client IP range, must not overlap with hub/spoke)</li>



<li><strong>Tunnel type</strong>:&nbsp;<strong>IKEv2 and OpenVPN (SSL)</strong></li>



<li><strong>Authentication type</strong>:&nbsp;<strong>Azure certificate</strong></li>



<li><strong>Root certificate name</strong>:&nbsp;<code>AzureVPNRootCert</code></li>



<li><strong>Public certificate data</strong>: Paste the base64 string from Step 1</li>
</ul>
</li>



<li>Click&nbsp;<strong>Save</strong>&nbsp;(this may take 10-15 minutes)</li>
</ol>



<p><strong>Step 3: Download VPN Client</strong></p>



<ol class="wp-block-list">
<li>After P2S configuration is saved, click&nbsp;<strong>Download VPN client</strong></li>



<li>Extract the downloaded ZIP file</li>



<li><strong>Windows</strong>: Run&nbsp;<code>WindowsAmd64/VpnClientSetupAmd64.exe</code></li>



<li><strong>macOS</strong>: Import&nbsp;<code>Generic/VpnServerRoot.cer</code>&nbsp;and client cert into Keychain, configure VPN in System Preferences</li>



<li><strong>Linux</strong>: Use the&nbsp;<code>Generic/VpnSettings.xml</code>&nbsp;with NetworkManager or strongSwan</li>
</ol>



<p><strong>Step 4: Connect to Azure VPN</strong></p>



<p>Windows:</p>



<ol class="wp-block-list">
<li>Go to&nbsp;<strong>Settings</strong>&nbsp;→&nbsp;<strong>Network &amp; Internet</strong>&nbsp;→&nbsp;<strong>VPN</strong></li>



<li>You&#8217;ll see a new VPN connection named after your VNet</li>



<li>Click&nbsp;<strong>Connect</strong></li>



<li>Your client certificate will be used for authentication</li>
</ol>



<p>macOS/Linux:</p>



<ol class="wp-block-list">
<li>Configure VPN client with the settings from&nbsp;<code>VpnSettings.xml</code></li>



<li>Import client certificate</li>



<li>Connect using native VPN client or OpenVPN</li>
</ol>



<h3 class="wp-block-heading">Option 2: P2S with Azure AD Authentication (Recommended for Enterprise)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#option-2-p2s-with-azure-ad-authentication-recommended-for-enterprise"></a></p>



<p>This method uses Azure AD for authentication (no certificates needed).</p>



<p><strong>Step 1: Register Azure VPN Application in Azure AD</strong></p>



<pre class="wp-block-preformatted"># Use the pre-registered Azure VPN application
# This is a Microsoft-provided app available in all Azure AD tenants
AZURE_VPN_APP_ID="41b23e61-6c1e-4545-b367-cd054e0ed4b4"

# Grant admin consent (one-time, requires admin)
az ad app permission grant \
  --id $AZURE_VPN_APP_ID \
  --api 00000003-0000-0000-c000-000000000000</pre>



<p><strong>Step 2: Configure P2S with Azure AD via Azure Portal</strong></p>



<ol class="wp-block-list">
<li>Go to&nbsp;<strong>Virtual network gateways</strong>&nbsp;→ Your gateway →&nbsp;<strong>Point-to-site configuration</strong></li>



<li><strong>Address pool</strong>:&nbsp;<code>172.16.0.0/24</code></li>



<li><strong>Tunnel type</strong>:&nbsp;<strong>OpenVPN (SSL)</strong></li>



<li><strong>Authentication type</strong>:&nbsp;<strong>Azure Active Directory</strong></li>



<li><strong>Tenant</strong>:&nbsp;<code>https://login.microsoftonline.com/{YOUR-TENANT-ID}/</code></li>



<li><strong>Audience</strong>:&nbsp;<code>41b23e61-6c1e-4545-b367-cd054e0ed4b4</code></li>



<li><strong>Issuer</strong>:&nbsp;<code>https://sts.windows.net/{YOUR-TENANT-ID}/</code></li>



<li>Click&nbsp;<strong>Save</strong></li>
</ol>



<p><strong>Step 3: Download Azure VPN Client</strong></p>



<ol class="wp-block-list">
<li>Download&nbsp;<strong>Azure VPN Client</strong>&nbsp;from:
<ul class="wp-block-list">
<li>Windows: Microsoft Store</li>



<li>macOS: App Store</li>



<li>Linux:&nbsp;<a href="https://aka.ms/azvpnclientdownload">Download from Microsoft</a></li>
</ul>
</li>



<li>After P2S configuration is saved in Azure Portal, click&nbsp;<strong>Download VPN client</strong></li>



<li>Extract and open the&nbsp;<code>AzureVPN</code>&nbsp;folder</li>



<li>Import the&nbsp;<code>azurevpnconfig.xml</code>&nbsp;file into Azure VPN Client</li>
</ol>



<p><strong>Step 4: Connect</strong></p>



<ol class="wp-block-list">
<li>Open Azure VPN Client</li>



<li>Click&nbsp;<strong>+</strong>&nbsp;→&nbsp;<strong>Import</strong></li>



<li>Select the&nbsp;<code>azurevpnconfig.xml</code>&nbsp;file</li>



<li>Click&nbsp;<strong>Connect</strong></li>



<li>Sign in with your Azure AD credentials</li>



<li>You&#8217;re now connected to Azure!</li>
</ol>



<h3 class="wp-block-heading">Verify P2S VPN Connection</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#verify-p2s-vpn-connection"></a></p>



<p>Once connected, test connectivity:</p>



<pre class="wp-block-preformatted"># Check your VPN IP (should be in the 172.16.0.0/24 range)
# Windows
ipconfig | findstr "172.16"

# macOS/Linux
ifconfig | grep "172.16"

# Test connectivity to Azure resources
ping 10.10.0.1        # Hub VNet gateway
ping 10.20.1.10       # Example spoke VM
ping 10.10.3.10       # Jumpbox (if enabled)

# Test DNS resolution
nslookup server.home.arpa  # Should resolve via Azure DNS</pre>



<h3 class="wp-block-heading">Troubleshooting P2S VPN</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#troubleshooting-p2s-vpn"></a></p>



<p><strong>Problem</strong>: &#8220;The VPN client is not configured&#8221; error</p>



<p><strong>Solution</strong>: Ensure P2S configuration is complete on the VPN Gateway and you&#8217;ve downloaded the latest VPN client configuration.</p>



<p><strong>Problem</strong>: Certificate authentication fails</p>



<p><strong>Solution</strong>:</p>



<ul class="wp-block-list">
<li>Verify root certificate is uploaded correctly to Azure</li>



<li>Ensure client certificate is installed in your personal certificate store</li>



<li>Check that client certificate chains to the root certificate</li>
</ul>



<p><strong>Problem</strong>: Can connect but can&#8217;t access resources</p>



<p><strong>Solution</strong>:</p>



<pre class="wp-block-preformatted"># Check your routing table (Windows)
route print

# Verify Azure routes are added for 10.10.0.0/16 and 10.20.0.0/16
# Check NSG rules allow traffic from P2S address pool (172.16.0.0/24)</pre>



<p><strong>Problem</strong>: Azure AD authentication fails</p>



<p><strong>Solution</strong>:</p>



<ul class="wp-block-list">
<li>Verify you&#8217;re using the correct tenant ID</li>



<li>Ensure the Azure VPN app has admin consent</li>



<li>Check that your user account has access to the subscription</li>
</ul>



<h3 class="wp-block-heading">P2S vs S2S VPN Comparison</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#p2s-vs-s2s-vpn-comparison"></a></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Point-to-Site (P2S)</th><th>Site-to-Site (S2S)</th></tr></thead><tbody><tr><td><strong>Use Case</strong></td><td>Individual laptops/devices</td><td>On-premises network gateway</td></tr><tr><td><strong>Setup</strong></td><td>Per-device VPN client</td><td>Router/firewall configuration</td></tr><tr><td><strong>Authentication</strong></td><td>Certificates or Azure AD</td><td>Pre-shared key (IPSec)</td></tr><tr><td><strong>Users</strong></td><td>Remote workers, contractors</td><td>Entire office network</td></tr><tr><td><strong>Address Pool</strong></td><td>172.16.0.0/24 (P2S range)</td><td>On-premises CIDR (192.168.x.x)</td></tr><tr><td><strong>Concurrent</strong></td><td>Up to 10,000 connections (SKU dependent)</td><td>Single tunnel, always-on</td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Best Practice</strong>: Use&nbsp;<strong>both</strong>&nbsp;P2S and S2S together. S2S connects your office network to Azure, while P2S allows individual remote workers to connect from anywhere.</p>
</blockquote>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Push to GitHub Repository and use GitHub Actions for CI/CD</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-push-to-github-repository-and-use-github-actions-for-cicd"></a></p>



<p>After running the&nbsp;<code>bootstrap.sh</code>&nbsp;script and customizing your configuration, you&#8217;ll need to push your code to GitHub. Also this generated repository structure includes a GitHub Actions workflow (<code>.github/workflows/tofu.yml</code>) for automated deployment using OpenID Connect (OIDC) for secure, passwordless authentication to Azure. Follow the steps to configure your GitHub for the Actions workflow and push your code up to your GitHub repository.</p>



<p>=======</p>



<h2 class="wp-block-heading">� Create a New GitHub Repository</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-create-a-new-github-repository"></a></p>



<p>After running the&nbsp;<code>bootstrap.sh</code>&nbsp;script and customizing your configuration, you&#8217;ll need to push your code to GitHub. The next thing you will want to do is make sure you have a Git Hub Repository. Here&#8217;s how to do it:</p>



<ol class="wp-block-list">
<li><strong>Create a new repository on GitHub</strong>:
<ul class="wp-block-list">
<li>Go to&nbsp;<a href="https://github.com/new">github.com/new</a></li>



<li>Name it (e.g.,&nbsp;<code>lab-azure-lab-opentofu</code>&nbsp;or use your custom prefix)</li>



<li>Choose&nbsp;<strong>Private</strong>&nbsp;repository</li>



<li>Do&nbsp;<strong>NOT</strong>&nbsp;initialize with README, .gitignore, or license</li>



<li>Click&nbsp;<strong>Create repository</strong></li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> GitHub Actions CI/CD</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-github-actions-cicd"></a></p>



<p>After you push the code to the new repository the code will include a GitHub Actions workflow (<code>.github/workflows/tofu.yml</code>) for automated deployment using OpenID Connect (OIDC) for secure, passwordless authentication to Azure.</p>



<h3 class="wp-block-heading">Workflow Triggers</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#workflow-triggers"></a></p>



<ul class="wp-block-list">
<li><strong>Pull Request</strong>: Runs&nbsp;<code>tofu fmt</code>,&nbsp;<code>init</code>,&nbsp;<code>validate</code>, and&nbsp;<code>plan</code></li>



<li><strong>Push to main</strong>: Runs full deployment with&nbsp;<code>tofu apply</code></li>
</ul>



<h3 class="wp-block-heading">Step 1: Create Azure Service Principal with Federated Credentials</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-1-create-azure-service-principal-with-federated-credentials"></a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Where to Run This</strong>: Run these commands from your&nbsp;<strong>local terminal</strong>&nbsp;(bash, PowerShell, or any shell where you have Azure CLI installed).</p>



<p><strong>Prerequisites</strong>:</p>



<ul class="wp-block-list">
<li>Azure CLI installed</li>



<li>Logged into Azure:&nbsp;<code>az login</code></li>



<li>Correct subscription selected:&nbsp;<code>az account set --subscription "YOUR_SUBSCRIPTION"</code></li>
</ul>
</blockquote>



<p>Create a service principal that GitHub Actions can use to authenticate to Azure via OIDC:</p>



<pre class="wp-block-preformatted"># Set variables (replace with your values)
GITHUB_ORG="YourGitHubUsername"      
GITHUB_REPO="customprefix-azure-lab-opentofu"  # Your repo name with custom prefix
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
APP_NAME="customprefix-azure-lab-opentofu" # Use repo name with custom prefix

# Create the service principal
az ad app create --display-name $APP_NAME

# Get the Application (client) ID
CLIENT_ID=$(az ad app list --display-name $APP_NAME --query "[0].appId" -o tsv)
OBJECT_ID=$(az ad app list --display-name $APP_NAME --query "[0].id" -o tsv)

# Create a service principal for the app
az ad sp create --id $CLIENT_ID

# Add federated credential for GitHub Actions (main branch)
az ad app federated-credential create \
  --id $OBJECT_ID \
  --parameters "{
    \"name\": \"github-actions-main\",
    \"issuer\": \"https://token.actions.githubusercontent.com\",
    \"subject\": \"repo:${GITHUB_ORG}/${GITHUB_REPO}:ref:refs/heads/main\",
    \"description\": \"GitHub Actions - main branch\",
    \"audiences\": [\"api://AzureADTokenExchange\"]
  }"

# Add federated credential for GitHub Actions (pull requests)
az ad app federated-credential create \
  --id $OBJECT_ID \
  --parameters "{
    \"name\": \"github-actions-pr\",
    \"issuer\": \"https://token.actions.githubusercontent.com\",
    \"subject\": \"repo:${GITHUB_ORG}/${GITHUB_REPO}:pull_request\",
    \"description\": \"GitHub Actions - pull requests\",
    \"audiences\": [\"api://AzureADTokenExchange\"]
  }"

# Assign Contributor role to the service principal on your subscription
az role assignment create \
  --assignee $CLIENT_ID \
  --role Contributor \
  --scope /subscriptions/$SUBSCRIPTION_ID

# Get the service principal object ID for storage access
SP_OBJECT_ID=$(az ad sp show --id $CLIENT_ID --query id -o tsv)

# Grant service principal access to state storage (from bootstrap.sh output)
# Replace STORAGE_ACCOUNT_NAME with the value from bootstrap.sh output
STORAGE_ACCOUNT_NAME="tfstate12345"  # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> REPLACE with your actual storage account name
TFSTATE_RG="rg-tfstate"

az role assignment create \
  --assignee-object-id $SP_OBJECT_ID \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$TFSTATE_RG/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT_NAME"

# Display the values you'll need for GitHub Secrets
echo "======================================"
echo "GitHub Secrets Configuration:"
echo "======================================"
echo "AZURE_CLIENT_ID: $CLIENT_ID"
echo "AZURE_TENANT_ID: $(az account show --query tenantId -o tsv)"
echo "AZURE_SUBSCRIPTION_ID: $SUBSCRIPTION_ID"
echo ""
echo "State Storage (from bootstrap.sh):"
echo "TFSTATE_STORAGE_ACCOUNT: $STORAGE_ACCOUNT_NAME"
echo "TFSTATE_RESOURCE_GROUP: $TFSTATE_RG"
echo "TFSTATE_CONTAINER: tfstate"
echo "======================================"</pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Important Notes</strong>:</p>



<ul class="wp-block-list">
<li><strong>You only run this ONCE</strong>&nbsp;&#8211; It creates the Azure resources needed for GitHub Actions</li>



<li><strong>Replace STORAGE_ACCOUNT_NAME</strong>&nbsp;&#8211; Use the actual storage account name from&nbsp;<code>bootstrap.sh</code>&nbsp;output</li>



<li><strong>Requires permissions</strong>&nbsp;&#8211; You need Azure AD permissions to create service principals</li>



<li><strong>Save the output</strong>&nbsp;&#8211; Copy all values for GitHub Secrets (Step 2)</li>



<li><strong>Security</strong>&nbsp;&#8211; These credentials use OIDC (no passwords stored in GitHub)</li>



<li><strong>Storage access is critical</strong>&nbsp;&#8211; Without it, GitHub Actions cannot read/write state files</li>
</ul>
</blockquote>



<h3 class="wp-block-heading">Step 2: Configure GitHub Repository Secrets</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-2-configure-github-repository-secrets"></a></p>



<p>Go to your GitHub repository:</p>



<ol class="wp-block-list">
<li>Navigate to&nbsp;<strong>Settings</strong>&nbsp;→&nbsp;<strong>Secrets and variables</strong>&nbsp;→&nbsp;<strong>Actions</strong></li>



<li>Click&nbsp;<strong>New repository secret</strong></li>



<li>Add the following secrets (from Step 1 output and bootstrap.sh output):</li>
</ol>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Secret Name</th><th>Value</th><th>Description</th></tr></thead><tbody><tr><td><code>AZURE_CLIENT_ID</code></td><td>From step 1 output</td><td>Service Principal Application ID</td></tr><tr><td><code>AZURE_TENANT_ID</code></td><td>From step 1 output</td><td>Your Azure tenant ID</td></tr><tr><td><code>AZURE_SUBSCRIPTION_ID</code></td><td>From step 1 output</td><td>Your Azure subscription ID</td></tr><tr><td><code>TFSTATE_STORAGE_ACCOUNT</code></td><td>From bootstrap.sh output</td><td>State storage account name (e.g.,&nbsp;<code>tfstatelab123456</code>&nbsp;or&nbsp;<code>tfstate{your-prefix}123456</code>)</td></tr><tr><td><code>TFSTATE_RESOURCE_GROUP</code></td><td><code>rg-tfstate-{prefix}</code></td><td>State storage resource group (e.g.,&nbsp;<code>rg-tfstate-lab</code>&nbsp;or&nbsp;<code>rg-tfstate-myproject</code>)</td></tr><tr><td><code>TFSTATE_CONTAINER</code></td><td><code>tfstate</code></td><td>State storage container name</td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Note</strong>: The last three secrets are for remote state management. GitHub Actions needs these to access the Azure Storage Account where your OpenTofu state is stored. The resource group and storage account names will include your custom prefix if you specified one during bootstrap.</p>
</blockquote>



<h3 class="wp-block-heading">Step 3: Push Code to GitHub</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-3-push-code-to-github"></a></p>



<p>Now that you have your GitHub repository and the action secrets set next we need to push our code to the repository. Here&#8217;s how to do it from VS Code or your terminal:</p>



<h3 class="wp-block-heading">Option 1: Push to New Repository</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#option-1-push-to-new-repository"></a></p>



<ol class="wp-block-list">
<li><strong>Initialize Git and push from your local folder</strong>:</li>
</ol>



<pre class="wp-block-preformatted"># Navigate to your project folder (replace 'lab' with your prefix)
cd lab-azure-lab-opentofu

# Initialize Git repository (if not already done by bootstrap.sh)
git init

# Add the GitHub repository as remote
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git

# Create and switch to main branch
git checkout -b main

# Stage all files
git add -A

# Commit with descriptive message
git commit -m "Initial commit: Azure hub-spoke infrastructure with OpenTofu"

# Set main branch
git branch -M main

# Push to GitHub
git push -u origin main</pre>



<h3 class="wp-block-heading">Option 2: Push to Feature Branch</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#option-2-push-to-feature-branch"></a></p>



<p>If you already have a repository and want to push to a specific branch:</p>



<pre class="wp-block-preformatted"># Navigate to your project folder
cd azure-lab-opentofu

# Initialize Git if needed
git init

# Add remote (skip if already added)
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git

# Fetch existing branches
git fetch origin

# Create and switch to your feature branch
git checkout -b Az-Storage-for-state-Mgt

# If the branch exists remotely, pull and merge
git pull origin Az-Storage-for-state-Mgt --allow-unrelated-histories

# Stage all files
git add -A

# Commit your changes
git commit -m "Add Azure Storage backend for remote state management

- Integrated Azure Storage Account creation into bootstrap.sh
- Updated bootstrap-steps.sh with storage access commands
- Updated README.md with comprehensive remote state documentation
- Configured backend block in main.tf for state management"

# Push to GitHub
git push -u origin Az-Storage-for-state-Mgt</pre>



<h3 class="wp-block-heading">Using VS Code Source Control</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#using-vs-code-source-control"></a></p>



<p>If you prefer using VS Code&#8217;s built-in Git tools:</p>



<ol class="wp-block-list">
<li><strong>Open VS Code</strong>&nbsp;in your project folder</li>



<li><strong>Source Control panel</strong>&nbsp;(Ctrl+Shift+G or Cmd+Shift+G on Mac)</li>



<li><strong>Stage changes</strong>: Click the&nbsp;<code>+</code>&nbsp;icon next to &#8220;Changes&#8221;</li>



<li><strong>Commit</strong>: Enter commit message and click the checkmark ✓</li>



<li><strong>Publish/Push</strong>: Click &#8220;Publish Branch&#8221; or &#8220;Push&#8221; button in the status bar</li>
</ol>



<h3 class="wp-block-heading">Important Notes</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#important-notes"></a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Before pushing</strong>:</p>



<ul class="wp-block-list">
<li>Ensure&nbsp;<code>terraform.tfvars</code>&nbsp;does&nbsp;<strong>NOT</strong>&nbsp;contain sensitive data (VPN keys, passwords)</li>



<li>Verify&nbsp;<code>.gitignore</code>&nbsp;excludes&nbsp;<code>.tfstate</code>&nbsp;files and secrets</li>



<li>Review what&#8217;s being committed:&nbsp;<code>git status</code></li>
</ul>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Authentication</strong>:</p>



<ul class="wp-block-list">
<li>For HTTPS: You&#8217;ll be prompted for GitHub username and Personal Access Token (PAT)</li>



<li>For SSH: Set up SSH keys first:&nbsp;<a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh">GitHub SSH setup</a></li>



<li>Recommended: Use SSH for easier authentication</li>
</ul>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Next Steps After Pushing</strong>:</p>



<ul class="wp-block-list">
<li>Create a Pull Request if using feature branches</li>



<li>Set up GitHub Actions (see next section)</li>



<li>Add required GitHub Secrets for CI/CD</li>
</ul>
</blockquote>



<p>=======</p>



<h3 class="wp-block-heading">Step 3.5: State Migration (If You Deployed Locally First)</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-35-state-migration-if-you-deployed-locally-first"></a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>CRITICAL</strong>: If you deployed infrastructure locally with&nbsp;<code>tofu apply</code>&nbsp;BEFORE setting up GitHub Actions, you must migrate your state to Azure Storage.</p>
</blockquote>



<p><strong>Why?</strong>&nbsp;Your local state file contains all your deployed resources. If you don&#8217;t migrate it to Azure Storage, GitHub Actions will start with an empty state and try to create everything again, causing &#8220;resource already exists&#8221; errors.</p>



<p><strong>How to migrate:</strong></p>



<ol class="wp-block-list">
<li><strong>Check if you have a local state file:</strong></li>
</ol>



<pre class="wp-block-preformatted">cd azure-lab-opentofu/envs/lab
ls -lh terraform.tfstate*</pre>



<p>If you see&nbsp;<code>terraform.tfstate</code>&nbsp;or&nbsp;<code>terraform.tfstate.backup</code>&nbsp;with meaningful size (50KB+), you need to migrate.</p>



<ol start="2" class="wp-block-list">
<li><strong>Initialize with state migration:</strong></li>
</ol>



<pre class="wp-block-preformatted">cd azure-lab-opentofu/envs/lab
tofu init -migrate-state</pre>



<p>OpenTofu will ask:&nbsp;<code>Do you want to copy existing state to the new backend?</code><br>Type&nbsp;<code>yes</code>&nbsp;and press Enter.</p>



<ol start="3" class="wp-block-list">
<li><strong>Verify the state was uploaded to Azure Storage:</strong></li>
</ol>



<pre class="wp-block-preformatted">az storage blob list \
  --account-name &lt;TFSTATE_STORAGE_ACCOUNT&gt; \
  --container-name tfstate \
  --auth-mode key \
  --query "[].{name:name, size:properties.contentLength}" -o table</pre>



<p>You should see&nbsp;<code>lab.tfstate</code>&nbsp;with a size around 50KB or more (not empty!).</p>



<ol start="4" class="wp-block-list">
<li><strong>Optional: Remove local state files (they&#8217;re now in Azure Storage):</strong></li>
</ol>



<pre class="wp-block-preformatted">rm terraform.tfstate terraform.tfstate.backup</pre>



<p><strong>Alternative Approach: Deploy from GitHub Actions First</strong></p>



<p>If you haven&#8217;t deployed locally yet:</p>



<ol class="wp-block-list">
<li>Skip local deployment</li>



<li>Set up service principal and GitHub Secrets (Steps 1-2)</li>



<li>Push code to GitHub (Step 3)</li>



<li>Let GitHub Actions deploy everything</li>



<li>Then you can work locally by running&nbsp;<code>tofu init</code>&nbsp;(it will use the state from Azure Storage)</li>
</ol>



<p>This approach is cleaner and avoids state migration!</p>



<h3 class="wp-block-heading">Step 4: Trigger GitHub Actions Workflow</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-4-trigger-github-actions-workflow"></a></p>



<p><strong>Option 1: Automatic trigger on push to main</strong></p>



<pre class="wp-block-preformatted"># Make a change, commit, and push
git add .
git commit -m "Update infrastructure configuration"
git push</pre>



<p><strong>Option 2: Create a Pull Request</strong></p>



<pre class="wp-block-preformatted"># Create a new branch
git checkout -b feature/update-firewall-rules

# Make changes to .tf files
# ... edit files ...

# Commit and push
git add .
git commit -m "Add new firewall rules"
git push -u origin feature/update-firewall-rules

# Go to GitHub and create a Pull Request
# The workflow will run automatically and comment the plan</pre>



<p><strong>Option 3: Manual trigger via GitHub UI</strong></p>



<ol class="wp-block-list">
<li>Go to your repository on GitHub</li>



<li>Click&nbsp;<strong>Actions</strong>&nbsp;tab</li>



<li>Select the&nbsp;<strong>opentofu</strong>&nbsp;workflow</li>



<li>Click&nbsp;<strong>Run workflow</strong>&nbsp;dropdown (requires workflow_dispatch trigger &#8211; add to workflow if needed)</li>
</ol>



<h3 class="wp-block-heading">Step 5: Monitor Workflow Execution</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#step-5-monitor-workflow-execution"></a></p>



<ol class="wp-block-list">
<li>Go to&nbsp;<strong>Actions</strong>&nbsp;tab in your GitHub repository</li>



<li>Click on the running workflow</li>



<li>View logs for each step:
<ul class="wp-block-list">
<li><strong>Plan job</strong>&nbsp;(on PR): Shows&nbsp;<code>tofu plan</code>&nbsp;output</li>



<li><strong>Apply job</strong>&nbsp;(on push to main): Shows&nbsp;<code>tofu apply</code>&nbsp;output</li>
</ul>
</li>



<li>Check for errors or warnings in the logs</li>
</ol>



<h3 class="wp-block-heading">Workflow Features</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#workflow-features"></a></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>OIDC Authentication</strong>&nbsp;&#8211; No secrets or passwords stored</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Automatic formatting checks</strong>&nbsp;&#8211; Ensures code style consistency</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Validation on every PR</strong>&nbsp;&#8211; Catches errors before merge</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Plan preview</strong>&nbsp;&#8211; See changes before applying</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Auto-apply on main</strong>&nbsp;&#8211; Automatic deployment on merge</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Working directory</strong>&nbsp;&#8211; Runs from&nbsp;<code>envs/lab</code>&nbsp;automatically</li>
</ul>



<h3 class="wp-block-heading">Troubleshooting GitHub Actions</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#troubleshooting-github-actions"></a></p>



<p><strong>Problem</strong>: Workflow fails with &#8220;AADSTS700016: Application not found&#8221;</p>



<p><strong>Solution</strong>: Verify federated credentials are set correctly:</p>



<pre class="wp-block-preformatted"># List federated credentials
az ad app federated-credential list --id $OBJECT_ID

# Verify the subject matches your GitHub org/repo exactly</pre>



<p><strong>Problem</strong>: &#8220;Unauthorized&#8221; error during Azure login</p>



<p><strong>Solution</strong>: Verify role assignment:</p>



<pre class="wp-block-preformatted"># Check role assignments for the service principal
az role assignment list --assignee $CLIENT_ID --output table

# Re-assign if needed
az role assignment create \
  --assignee $CLIENT_ID \
  --role Contributor \
  --scope /subscriptions/$SUBSCRIPTION_ID</pre>



<p><strong>Problem</strong>: Workflow runs but&nbsp;<code>tofu init</code>&nbsp;fails</p>



<p><strong>Solution</strong>: Check if backend is configured. For first-time setup, ensure you&#8217;re using local backend or configure remote backend in&nbsp;<code>envs/lab/main.tf</code>.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9ea.png" alt="🧪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Testing &amp; Validation</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-testing--validation"></a></p>



<h3 class="wp-block-heading">After Deployment</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#after-deployment"></a></p>



<pre class="wp-block-preformatted"># Check all outputs
tofu output

# Verify VPN connection status
az network vnet-gateway show \
  --name lab-vpngw \
  --resource-group rg-lab-hubspoke \
  --query "vpnClientConfiguration"

# Check firewall status
az network firewall show \
  --name lab-azfw \
  --resource-group rg-lab-hubspoke \
  --query "provisioningState"

# List private endpoints
az network private-endpoint list \
  --resource-group rg-lab-hubspoke \
  --output table</pre>



<h3 class="wp-block-heading">Jumpbox Connectivity Testing</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#jumpbox-connectivity-testing"></a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Note</strong>: Jumpbox is&nbsp;<strong>disabled by default</strong>&nbsp;(<code>enable_jumpbox = false</code>). To test jumpbox connectivity, first enable it in&nbsp;<code>terraform.tfvars</code>&nbsp;and run&nbsp;<code>tofu apply</code>.</p>
</blockquote>



<p><strong>1. Connect to jumpbox via DNAT:</strong></p>



<pre class="wp-block-preformatted"># Get SSH command from outputs (returns null if disabled)
tofu output jumpbox_ssh_via_firewall

# Connect (example, when enabled)
ssh -p 2222 azureuser@&lt;firewall-public-ip&gt;

# If you have custom SSH key location:
ssh -i ~/.ssh/azure_lab_key -p 2222 azureuser@&lt;firewall-public-ip&gt;</pre>



<p><strong>2. Test connectivity from jumpbox:</strong></p>



<pre class="wp-block-preformatted"># Once connected to jumpbox, test various connections:

# Test internet connectivity
curl -I https://google.com

# Test DNS resolution (should use Azure DNS)
nslookup microsoft.com

# Test on-premises DNS (via DNS Private Resolver)
nslookup server.home.arpa

# Test connectivity to on-premises network (via VPN)
ping 192.168.1.1

# Test connectivity to spoke VMs (via VNet peering)
ping 10.20.1.10

# Test Azure CLI (if installed)
az account show

# Test private endpoint connectivity (ACR)
nslookup &lt;registry-name&gt;.azurecr.io  # Should resolve to private IP</pre>



<p><strong>3. Security verification:</strong></p>



<pre class="wp-block-preformatted"># Verify jumpbox uses firewall for egress
# From jumpbox, check route table:
ip route

# Should show default route pointing to firewall IP (10.10.0.4)</pre>



<h3 class="wp-block-heading">Test Connectivity</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#test-connectivity"></a></p>



<p>From an Azure VM in the spoke:</p>



<pre class="wp-block-preformatted"># Test internet via firewall
curl https://ifconfig.me

# Test on-premises connectivity
ping 192.168.1.1

# Test ACR private endpoint
nslookup acrlabunique12345.azurecr.io
# Should resolve to 10.20.3.x (private IP)

# Test Key Vault
nslookup kvlabunique12345.vault.azure.net
# Should resolve to 10.20.3.x (private IP)</pre>



<h2 class="wp-block-heading">Troubleshooting</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#troubleshooting"></a></p>



<h3 class="wp-block-heading">Deployment Mode Issues</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#deployment-mode-issues"></a></p>



<h4 class="wp-block-heading">FULL Mode Common Issues</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#full-mode-common-issues"></a></p>



<p><strong>Problem</strong>: VPN not connecting</p>



<p><strong>Solutions</strong>:</p>



<pre class="wp-block-preformatted"># 1. Verify onprem_public_ip matches your WAN IP
tofu output vpn_gateway_public_ip

# 2. Verify vpn_shared_key matches on-prem device exactly
# Check terraform.tfvars and your on-prem VPN configuration

# 3. Check VPN connection status
az network vpn-connection show \
  --name lab-vpn-connection \
  --resource-group rg-lab-hub-network \
  --query connectionStatus</pre>



<p><strong>Problem</strong>: DNS not resolving on-premises domains</p>



<p><strong>Solutions</strong>:</p>



<pre class="wp-block-preformatted"># 1. Verify onprem_dns_servers are reachable
# Test from jumpbox if deployed

# 2. Check forward_domain_name matches your internal domain
# In terraform.tfvars: forward_domain_name = "corp.local"

# 3. Verify DNS resolver status
az dns-resolver show \
  --name lab-dns-resolver \
  --resource-group rg-lab-hub-network \
  --query provisioningState</pre>



<p><strong>Problem</strong>: Cannot access private endpoints</p>



<p><strong>Solution</strong>: Verify you&#8217;re accessing from within Azure VNet or via VPN:</p>



<pre class="wp-block-preformatted"># Private endpoints are only accessible from:
# - Azure VMs in the VNet
# - On-premises via VPN connection
# - Cannot access from public internet</pre>



<h4 class="wp-block-heading">BASIC Mode Common Issues</h4>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#basic-mode-common-issues"></a></p>



<p><strong>Problem</strong>: Cannot access Key Vault or ACR</p>



<p><strong>Solution</strong>: Configure Azure RBAC and authenticate:</p>



<pre class="wp-block-preformatted"># 1. Assign yourself Key Vault Administrator role
az role assignment create \
  --assignee $(az ad signed-in-user show --query id -o tsv) \
  --role "Key Vault Administrator" \
  --scope $(az keyvault show --name &lt;key_vault_name&gt; --query id -o tsv)

# 2. For ACR, login via Azure CLI
az acr login --name &lt;acr_name&gt;

# 3. Verify public access is enabled
az keyvault show --name &lt;key_vault_name&gt; --query properties.publicNetworkAccess
# Should return: "Enabled"</pre>



<p><strong>Problem</strong>: Expected resources missing (VPN, DNS Resolver, Jumpbox)</p>



<p><strong>Solution</strong>: This is expected in BASIC mode:</p>



<pre class="wp-block-preformatted"># BASIC mode excludes these resources by design
# To use them, re-run bootstrap.sh and select FULL mode (Option 1)

cd bootstrap
./bootstrap.sh

# Select: 1) Full mode
# Then reconfigure terraform.tfvars with VPN and DNS settings</pre>



<p><strong>Problem</strong>: Module not found errors (dns-private-resolver, vpn-s2s, compute-jumpbox)</p>



<p><strong>Solution</strong>: These modules are not generated in BASIC mode:</p>



<pre class="wp-block-preformatted"># This is expected behavior
# If you need these modules, switch to FULL mode by:
# 1. Re-running bootstrap.sh and selecting FULL mode
# 2. Or manually creating the module directories and configurations</pre>



<p>=======</p>



<h3 class="wp-block-heading">Jumpbox Connection Issues</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#jumpbox-connection-issues"></a></p>



<p><strong>Problem</strong>: Jumpbox-related outputs return&nbsp;<code>null</code></p>



<p><strong>Solution</strong>: The jumpbox is disabled by default. To enable it:</p>



<pre class="wp-block-preformatted"># In terraform.tfvars, set:
enable_jumpbox = true

# Then re-run:
tofu apply</pre>



<p><strong>Problem</strong>: Cannot SSH to jumpbox via firewall</p>



<p><strong>Solutions</strong>:</p>



<pre class="wp-block-preformatted"># 1. Verify jumpbox is enabled
# In terraform.tfvars, ensure:
enable_jumpbox = true

# 2. Verify DNAT is enabled
enable_jumpbox_dnat = true

# 3. Verify firewall has public IP assigned
tofu output firewall_public_ip

# 4. Verify firewall provisioning state
az network firewall show \
  --name lab-azfw \
  --resource-group rg-lab-hub-network \
  --query "provisioningState"

# 5. Check DNAT rule exists
az network firewall nat-rule list \
  --resource-group rg-lab-hub-network \
  --firewall-name lab-azfw \
  --collection-name jumpbox-dnat

# 6. Verify your source IP is allowed (if configured)
# Check var.jumpbox_allowed_ssh_source in terraform.tfvars</pre>



<p><strong>Problem</strong>: VM creation fails with SKU capacity error</p>



<p><strong>Solution</strong>: Change to a different VM size:</p>



<pre class="wp-block-preformatted"># In terraform.tfvars, try:
jumpbox_vm_size = "Standard_B2s"      # or
jumpbox_vm_size = "Standard_D2s_v3"   # or
jumpbox_vm_size = "Standard_B1ms"     # (smaller/cheaper)

# Then re-run:
tofu apply</pre>



<p><strong>Problem</strong>: SSH connection times out</p>



<p><strong>Troubleshooting steps</strong>:</p>



<ol class="wp-block-list">
<li><strong>Check firewall NSG</strong>&nbsp;&#8211; Ensure port 2222 is allowed inbound</li>



<li><strong>Verify jumpbox is running</strong>&nbsp;&#8211; Check VM status in Azure Portal</li>



<li><strong>Test firewall connectivity</strong>:&nbsp;<code>nc -zv &lt;firewall-ip&gt; 2222</code></li>



<li><strong>Check your SSH key</strong>&nbsp;&#8211; Ensure you&#8217;re using the correct private key</li>



<li><strong>Firewall logs</strong>&nbsp;&#8211; Check Azure Firewall logs for denied connections</li>
</ol>



<p><strong>Problem</strong>: Can reach jumpbox but can&#8217;t connect to other resources</p>



<pre class="wp-block-preformatted"># From jumpbox, verify routing:
ip route
# Should show default route to firewall (10.10.0.4)

# Test DNS resolution:
nslookup &lt;acr-name&gt;.azurecr.io
# Should resolve to private IP (10.20.3.x)

# Test VPN connectivity:
ping 192.168.1.1
# If fails, check VPN Gateway status</pre>



<h3 class="wp-block-heading">VPN Connection Issues</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#vpn-connection-issues"></a></p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> What Makes This Different?</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-what-makes-this-different"></a></p>



<h3 class="wp-block-heading">Remote State Management with Azure Storage</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#remote-state-management-with-azure-storage"></a></p>



<p>This infrastructure uses&nbsp;<strong>Azure Storage Backend</strong>&nbsp;for OpenTofu state:</p>



<pre class="wp-block-preformatted">backend "azurerm" {
  resource_group_name  = "rg-tfstate"
  storage_account_name = "tfstate12345"  # Created by bootstrap.sh
  container_name       = "tfstate"
  key                  = "lab.tfstate"
  use_oidc             = true  # Passwordless authentication
}</pre>



<p><strong>Why Remote State?</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>State persistence</strong>&nbsp;between CI/CD runs (no more &#8220;already exists&#8221; errors)</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>State locking</strong>&nbsp;prevents concurrent modifications and corruption</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Team collaboration</strong>&nbsp;&#8211; multiple developers share the same state</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Secure</strong>&nbsp;&#8211; Encrypted at rest with Azure Storage encryption</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>OIDC authentication</strong>&nbsp;&#8211; No storage keys in GitHub secrets</li>
</ul>



<p><strong>Without remote state</strong>, GitHub Actions would lose track of infrastructure after each run, causing deployment failures.</p>



<h3 class="wp-block-heading">Plan-Safe&nbsp;<code>for_each</code>&nbsp;Patterns</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#plan-safe-for_each-patterns"></a></p>



<p>This code uses&nbsp;<strong>map-based&nbsp;<code>for_each</code></strong>&nbsp;instead of list-based approaches:</p>



<pre class="wp-block-preformatted"># <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Good: Map with stable keys
spoke_subnets = {
  apps = module.spoke1.apps_subnet_id
  apis = module.spoke1.apis_subnet_id
  data = module.spoke1.data_subnet_id
}

# <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Avoid: List causes plan instability
spoke_subnets = [
  module.spoke1.apps_subnet_id,
  module.spoke1.apis_subnet_id,
  module.spoke1.data_subnet_id
]</pre>



<p><strong>Why?</strong>&nbsp;Maps use stable keys, preventing unnecessary resource recreation when lists are reordered.</p>



<h3 class="wp-block-heading">Modular Design</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#modular-design"></a></p>



<p>Each infrastructure component is isolated in its own module:</p>



<ul class="wp-block-list">
<li>Easy to add/remove components</li>



<li>Reusable across different environments</li>



<li>Clear separation of concerns</li>



<li>Simplified testing and maintenance</li>
</ul>



<h3 class="wp-block-heading">Production-Ready Features</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#production-ready-features"></a></p>



<ul class="wp-block-list">
<li>Private endpoints (no public access to PaaS services)</li>



<li>Firewall-based traffic inspection</li>



<li>Hybrid DNS resolution</li>



<li>Site-to-site VPN for on-premises connectivity</li>



<li>GitHub Actions for CI/CD</li>



<li>Comprehensive outputs for integration</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Customization</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#%EF%B8%8F-customization"></a></p>



<h3 class="wp-block-heading">Add More Spoke VNets</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#add-more-spoke-vnets"></a></p>



<ol class="wp-block-list">
<li>Copy the spoke module call in&nbsp;<code>envs/lab/main.tf</code>:</li>
</ol>



<pre class="wp-block-preformatted">module "spoke2" {
  source = "../../modules/network-spoke"
  
  name_prefix         = "${var.name_prefix}-2"
  location            = var.location
  resource_group_name = azurerm_resource_group.rg.name
  
  vnet_cidr        = "10.30.0.0/16"
  apps_subnet_cidr = "10.30.1.0/24"
  apis_subnet_cidr = "10.30.2.0/24"
  data_subnet_cidr = "10.30.3.0/24"
}</pre>



<ol start="2" class="wp-block-list">
<li>Add peering to the hub in the peering module call:</li>
</ol>



<pre class="wp-block-preformatted">module "peering_routing" {
  source = "../../modules/peering-routing"
  
  hub_vnet_name           = module.hub.vnet_name
  hub_vnet_id             = module.hub.vnet_id
  hub_resource_group_name = azurerm_resource_group.hub_network.name
  
  spoke_vnets = {
    spoke1 = {
      vnet_name           = module.spoke1.vnet_name
      vnet_id             = module.spoke1.vnet_id
      resource_group_name = azurerm_resource_group.spoke_network.name
    }
    spoke2 = {
      vnet_name           = module.spoke2.vnet_name
      vnet_id             = module.spoke2.vnet_id
      resource_group_name = azurerm_resource_group.spoke_network.name
    }
  }
  
  # Spoke subnets to route through firewall
  spoke_subnets = {
    spoke1_apps = module.spoke1.apps_subnet_id
    spoke1_apis = module.spoke1.apis_subnet_id
    spoke1_data = module.spoke1.data_subnet_id
    spoke2_apps = module.spoke2.apps_subnet_id
    spoke2_apis = module.spoke2.apis_subnet_id
    spoke2_data = module.spoke2.data_subnet_id
  }
  
  firewall_private_ip = module.firewall.firewall_private_ip
  
  depends_on = [
    module.hub,
    module.spoke1,
    module.spoke2,
    module.firewall
  ]
}</pre>



<h3 class="wp-block-heading">Change Azure Region</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#change-azure-region"></a></p>



<p>Update&nbsp;<code>location</code>&nbsp;in&nbsp;<code>terraform.tfvars</code>:</p>



<pre class="wp-block-preformatted">location = "eastus"  # or any Azure region</pre>



<h3 class="wp-block-heading">Adjust VPN Gateway SKU</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#adjust-vpn-gateway-sku"></a></p>



<p>For better performance or more features:</p>



<pre class="wp-block-preformatted">vpn_gateway_sku = "VpnGw2"  # or VpnGw3, VpnGw4, VpnGw5</pre>



<h3 class="wp-block-heading">Add Firewall Rules</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#add-firewall-rules"></a></p>



<p>Edit&nbsp;<code>modules/security-firewall/main.tf</code>&nbsp;to add rules:</p>



<pre class="wp-block-preformatted">resource "azurerm_firewall_network_rule_collection" "example" {
  name                = "example-rules"
  azure_firewall_name = azurerm_firewall.fw.name
  resource_group_name = var.resource_group_name
  priority            = 100
  action              = "Allow"

  rule {
    name                  = "allow-web"
    source_addresses      = ["10.20.0.0/16"]
    destination_ports     = ["80", "443"]
    destination_addresses = ["*"]
    protocols             = ["TCP"]
  }
}</pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> License</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-license"></a></p>



<p>The code in this repo and blog post are provided as-is for educational and lab purposes.</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Use Cases</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-use-cases"></a></p>



<p>This infrastructure is ideal for:</p>



<ul class="wp-block-list">
<li><strong>Hybrid Cloud Labs</strong>: Learn Azure networking with on-premises connectivity</li>



<li><strong>Development Environments</strong>: Isolated, secure environments for app development</li>



<li><strong>POC/Testing</strong>: Validate Azure services before production deployment</li>



<li><strong>Learning Platform</strong>: Hands-on experience with Azure networking concepts</li>



<li><strong>Home Lab Extension</strong>: Connect home lab to Azure securely</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Troubleshooting</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#-troubleshooting"></a></p>



<h3 class="wp-block-heading">VPN Not Connecting</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#vpn-not-connecting"></a></p>



<ol class="wp-block-list">
<li>Verify shared key matches on both sides</li>



<li>Check that on-premises public IP is correct</li>



<li>Ensure firewall allows UDP 500 and 4500</li>



<li>Review connection status in Azure Portal</li>
</ol>



<h3 class="wp-block-heading">DNS Not Resolving</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#dns-not-resolving"></a></p>



<ol class="wp-block-list">
<li>Verify DNS resolver is provisioned</li>



<li>Check forwarding rules configuration</li>



<li>Ensure VNet links are properly configured</li>



<li>Test with&nbsp;<code>nslookup</code>&nbsp;from Azure VM</li>
</ol>



<h3 class="wp-block-heading">Private Endpoints Not Working</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#private-endpoints-not-working"></a></p>



<ol class="wp-block-list">
<li>Verify private DNS zones are linked to VNets</li>



<li>Check private endpoint provisioning state</li>



<li>Ensure subnet has&nbsp;<code>private_endpoint_network_policies = "Disabled"</code></li>



<li>Test with&nbsp;<code>nslookup</code>&nbsp;to verify private IP resolution</li>
</ol>



<h3 class="wp-block-heading">Deployment Errors</h3>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#deployment-errors"></a></p>



<pre class="wp-block-preformatted"># Enable detailed logging
export TF_LOG=DEBUG

# Re-run with verbose output
tofu apply</pre>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5d1.png" alt="🗑" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cleanup</h2>



<p><a href="https://github.com/Buchatech/opentofu-azure-hubspoke-priv#%EF%B8%8F-cleanup"></a></p>



<p>To destroy all resources:</p>



<pre class="wp-block-preformatted">cd azure-lab-opentofu/envs/lab
tofu destroy</pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Warning</strong>: This will permanently delete all resources. Confirm you have backups of any important data.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>Built by S.Buchanan (<a href="https://www.buchatech.com/">www.buchatech.com</a>&nbsp;)</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>The goal of this solution is to give you a practical, production-style foundation for running Azure being: secure, private, and fully automated. Whether you are building a personal lab, a proof of concept, or the baseline for a real enterprise environment, this hub-and-spoke OpenTofu setup gives you something you can trust and extend. The goal is not just to deploy infrastructure, but to make it repeatable, auditable, and easy to evolve as your needs grow.<br><br>Again you can access the GitHub Repository of my &#8220;<strong>OpenTofu Azure Hub and Spoke</strong>&#8221; solution here: <a href="https://github.com/Buchatech/OpenTofu-Azure-HubSpoke-public" target="_blank" rel="noreferrer noopener">https://github.com/Buchatech/OpenTofu-Azure-HubSpoke-public</a></p>



<p>Thanks for reading!</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Docker Hardened Images Are Now Free: What This Means for Developers and Platform Teams</title>
		<link>https://www.buchatech.com/2025/12/docker-hardened-images-are-now-free-what-this-means-for-developers-and-platform-teams/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Mon, 22 Dec 2025 06:27:44 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9483</guid>

					<description><![CDATA[Last week Docker made a big move for the container ecosystem. Docker Hardened Images (DHI) are now free and open source, making secure container foundations accessible to everyone. If you build, deploy, or operate containerized workloads, this is one of those changes that quietly but meaningfully improves day to day security and reliability. Let’s break ... <a title="Docker Hardened Images Are Now Free: What This Means for Developers and Platform Teams" class="read-more" href="https://www.buchatech.com/2025/12/docker-hardened-images-are-now-free-what-this-means-for-developers-and-platform-teams/" aria-label="Read more about Docker Hardened Images Are Now Free: What This Means for Developers and Platform Teams">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Last week Docker made a big move for the container ecosystem. <strong>Docker Hardened Images (DHI) are now free and open source</strong>, making secure container foundations accessible to everyone.</p>



<p>If you build, deploy, or operate containerized workloads, this is one of those changes that quietly but meaningfully improves day to day security and reliability.</p>



<p>Let’s break down what Docker Hardened Images are, why they matter, and how you can start using them today.</p>



<h2 class="wp-block-heading">What Are Docker Hardened Images?</h2>



<p>Docker Hardened Images are <strong>base container images that come pre-hardened for security and transparency</strong>. Instead of starting from a generic base image and layering on your own security practices, DHI gives you a safer starting point out of the box.</p>



<p>They are designed to reduce common container risks without adding operational overhead or complexity.</p>



<p>In practical terms, this means Docker has already done the work many teams struggle to keep up with.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What You Get Out of the Box</h2>



<p>When you use Docker Hardened Images, your base images now:</p>



<ul class="wp-block-list">
<li>Include automated security metadata</li>



<li>Are minimalist and optimized for faster builds and startup times</li>



<li>Contain significantly fewer known vulnerabilities (CVEs) from the start</li>



<li>Are fully free and open source</li>
</ul>



<p>This shifts container security left, right to the foundation of your application images.<br><br>There still is a paid version of Docker Hardened Images for those that have enterprise needs. Here is a breakdown of what you get with the Free Docker Hardened Images and the Paid version. </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="882" height="342" src="https://www.buchatech.com/wp-content/uploads/2025/12/image-7.png" alt="" class="wp-image-9485" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image-7.png 882w, https://www.buchatech.com/wp-content/uploads/2025/12/image-7-300x116.png 300w, https://www.buchatech.com/wp-content/uploads/2025/12/image-7-768x298.png 768w" sizes="auto, (max-width: 882px) 100vw, 882px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why This Is a Big Deal</h2>



<p>Most container vulnerabilities originate from base images. Teams often inherit outdated packages, unused libraries, or poorly maintained dependencies without realizing it.</p>



<p>Docker Hardened Images help address that by:</p>



<ul class="wp-block-list">
<li>Reducing the attack surface before you write any application code</li>



<li>Improving transparency into what is inside your images</li>



<li>Lowering the burden on platform and security teams</li>



<li>Making secure defaults accessible even to small teams and solo developers</li>
</ul>



<p>Security becomes the baseline rather than an afterthought.</p>



<span id="more-9483"></span>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Who Should Use Docker Hardened Images?</h2>



<p>Docker Hardened Images are especially valuable if you:</p>



<ul class="wp-block-list">
<li>Run containers in production Kubernetes or Docker environments</li>



<li>Support regulated or security sensitive workloads</li>



<li>Want to reduce CVE noise in vulnerability scans</li>



<li>Are tired of maintaining custom hardened base images</li>



<li>Are teaching or learning container best practices and want good defaults</li>
</ul>



<p>They are a strong fit for both enterprise platforms and individual developers.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Getting Started with Docker Hardened Images</h2>



<p>Docker provides clear documentation on how to use Hardened Images as your base images:</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Docker Hardened Images Documentation<br><a href="https://docs.docker.com/dhi/">https://docs.docker.com/dhi/</a></p>



<p>You can also read Docker’s official announcement here:</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Docker Makes Hardened Images Free, Open, and Transparent<br><a href="https://www.docker.com/press-release/docker-makes-hardened-images-free-open-and-transparent-for-everyone/">https://www.docker.com/press-release/docker-makes-hardened-images-free-open-and-transparent-for-everyone/</a></p>



<p>The adoption model is simple. You swap your existing base image for a hardened one and continue building as usual. Here is a visual for getting started using Docker Hardened Images: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="683" src="https://www.buchatech.com/wp-content/uploads/2025/12/image-6-1024x683.png" alt="" class="wp-image-9484" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image-6-1024x683.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/12/image-6-300x200.png 300w, https://www.buchatech.com/wp-content/uploads/2025/12/image-6-768x512.png 768w, https://www.buchatech.com/wp-content/uploads/2025/12/image-6.png 1536w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Final Thoughts</h2>



<p>This is a strong signal from Docker that <strong>secure-by-default containers should be the norm, not a premium feature</strong>.</p>



<p>By making Docker Hardened Images free and open source, Docker lowers the barrier for better security practices across the entire ecosystem. Whether you are running production SaaS platforms or learning containers for the first time, this is an easy win.</p>



<p>If you are building cloud-native applications, now is a great time to revisit your base images and tighten things up without adding friction.</p>



<p>Thanks for reading. Check back soon for more blogs on Docker, Containers, Kubernetes, cloud, and AI. </p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>&#8220;Building Apps with OpenAI&#8221; my 29th Pluralsight Course!</title>
		<link>https://www.buchatech.com/2025/12/building-applications-with-openai-my-29th-pluralsight-course/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Thu, 11 Dec 2025 06:29:29 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Pluralsight]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Gen AI]]></category>
		<category><![CDATA[Generative AI]]></category>
		<category><![CDATA[Github]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[OpenAI]]></category>
		<category><![CDATA[Render]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9464</guid>

					<description><![CDATA[I am excited to share that my 29th Pluralsight course is now live titled Building Applications with OpenAI. This course guides developers through creating modern AI powered applications using OpenAI APIs. Whether you are just getting started with generative AI or looking to integrate it into real projects, you will walk away with practical skills ... <a title="&#8220;Building Apps with OpenAI&#8221; my 29th Pluralsight Course!" class="read-more" href="https://www.buchatech.com/2025/12/building-applications-with-openai-my-29th-pluralsight-course/" aria-label="Read more about &#8220;Building Apps with OpenAI&#8221; my 29th Pluralsight Course!">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I am excited to share that my 29th Pluralsight course is now live titled <strong>Building Applications with OpenAI</strong>. This course guides developers through creating modern AI powered applications using OpenAI APIs. Whether you are just getting started with generative AI or looking to integrate it into real projects, you will walk away with practical skills you can use right away.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="576" height="1024" src="https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline-576x1024.png" alt="" class="wp-image-9467" style="aspect-ratio:0.5625107185731435;width:300px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline-576x1024.png 576w, https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline-169x300.png 169w, https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline-768x1365.png 768w, https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline-864x1536.png 864w, https://www.buchatech.com/wp-content/uploads/2025/12/BuildOpenAIApp2-outline.png 939w" sizes="auto, (max-width: 576px) 100vw, 576px" /></figure>



<p>This was a fun course to build. In this course you will learn how to integrate OpenAI into real world applications from end to end. We begin by setting up the OpenAI API, handling authentication, and designing effective prompts. Then we build a full stack web app that uses AI to analyze and classify data while exploring best practices for deployment, performance monitoring, and error handling. By the end you will have the confidence to build, deploy, and scale your own AI driven solutions.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.buchatech.com/wp-content/uploads/2025/12/image-2-1024x559.png" alt="" class="wp-image-9468" style="aspect-ratio:1.8318905333705668;width:612px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image-2-1024x559.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/12/image-2-300x164.png 300w, https://www.buchatech.com/wp-content/uploads/2025/12/image-2-768x419.png 768w, https://www.buchatech.com/wp-content/uploads/2025/12/image-2-1536x839.png 1536w, https://www.buchatech.com/wp-content/uploads/2025/12/image-2.png 2036w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h5 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Why This Course Matters</strong></h5>



<p>Generative AI is reshaping how software gets built and developers are expected to know how to integrate these capabilities into applications. This course gives you the foundational and practical knowledge to do that. You will see how to handle prompt refinement, token limits, deployment tradeoffs, and optimization strategies.</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d8.png" alt="📘" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Official Course Description</strong></p>



<p>&#8220;<em>Generative AI is changing how software is developed, and developers are now expected to integrate AI features into modern applications. In this course, Building Applications with OpenAI, you’ll gain the skills to build, deploy, and maintain AI-powered web applications. First, you’ll explore how to configure the OpenAI API, manage authentication, and craft effective prompts. Next, you’ll build a full-stack expense tracking app that uses OpenAI to analyze and categorize expenses. Finally, you’ll learn how to deploy your app using platforms like Render or Google Cloud, monitor performance, and handle challenges such as token limits, error handling, and prompt optimization. When you’re finished with this course, you’ll have the knowledge and tools to confidently integrate OpenAI into your own applications and bring AI capabilities to your development projects.</em>&#8220;</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="626" src="https://www.buchatech.com/wp-content/uploads/2025/12/image-1-1024x626.png" alt="" class="wp-image-9466" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image-1-1024x626.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/12/image-1-300x183.png 300w, https://www.buchatech.com/wp-content/uploads/2025/12/image-1-768x470.png 768w, https://www.buchatech.com/wp-content/uploads/2025/12/image-1-1536x939.png 1536w, https://www.buchatech.com/wp-content/uploads/2025/12/image-1-2048x1252.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="985" height="1019" src="https://www.buchatech.com/wp-content/uploads/2025/12/image.png" alt="" class="wp-image-9465" style="aspect-ratio:0.9666427216297361;width:579px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image.png 985w, https://www.buchatech.com/wp-content/uploads/2025/12/image-290x300.png 290w, https://www.buchatech.com/wp-content/uploads/2025/12/image-768x795.png 768w" sizes="auto, (max-width: 985px) 100vw, 985px" /></figure>



<p>This course is a part of the &#8220;OpenAI for Developers Path&#8221; on Pluralsight. The path can be found here: <a href="https://app.pluralsight.com/paths/skills/openai-for-developers" target="_blank" rel="noreferrer noopener">https://app.pluralsight.com/paths/skills/openai-for-developers</a> and has many courses that will teach you various aspects of bringing OpenAI into your applications.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="681" src="https://www.buchatech.com/wp-content/uploads/2025/12/image-3-1024x681.png" alt="" class="wp-image-9474" style="aspect-ratio:1.5036818429271368;width:715px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/12/image-3-1024x681.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/12/image-3-300x199.png 300w, https://www.buchatech.com/wp-content/uploads/2025/12/image-3-768x511.png 768w, https://www.buchatech.com/wp-content/uploads/2025/12/image-3-1536x1021.png 1536w, https://www.buchatech.com/wp-content/uploads/2025/12/image-3.png 1936w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><br>If you’re building applications and need to add AI, this course will help you. Check out the course here:</p>



<p><a href="https://www.pluralsight.com/courses/building-applications-openai" target="_blank" rel="noreferrer noopener">https://www.pluralsight.com/courses/building-applications-openai</a></p>



<p>I hope this course serves as a valuable resource in your AI journey. Thank you for your continued support, and&nbsp;<strong>Be sure to follow my profile on Pluralsight so you will be notified as I release new courses</strong>!&nbsp;</p>



<p>Here is the link to my&nbsp;<strong>Pluralsight profile to follow me</strong>:</p>



<p><a href="https://www.pluralsight.com/authors/steve-buchanan" target="_blank" rel="noreferrer noopener">https://www.pluralsight.com/authors/steve-buchanan</a></p>



<span id="more-9464"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>My First Docker Captain Summit Experience</title>
		<link>https://www.buchatech.com/2025/10/my-first-docker-captain-summit-experience/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Fri, 31 Oct 2025 20:39:16 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[Docker Captain]]></category>
		<category><![CDATA[opensource]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9422</guid>

					<description><![CDATA[As many of you know, I was honored to be named a Docker Captain earlier this year (2025). This week, I had the incredible opportunity to attend my very first Docker Captain Summit, and what an experience it was. The event reminded me a bit of the Microsoft MVP Summit, but with even closer access ... <a title="My First Docker Captain Summit Experience" class="read-more" href="https://www.buchatech.com/2025/10/my-first-docker-captain-summit-experience/" aria-label="Read more about My First Docker Captain Summit Experience">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>As many of you know, I was honored to be named a <strong>Docker Captain</strong> earlier this year (2025). This week, I had the incredible opportunity to attend my very first <strong>Docker Captain Summit</strong>, and what an experience it was.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="683" src="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-1024x683.jpg" alt="" class="wp-image-9433" style="width:564px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-1024x683.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-300x200.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-768x512.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-1536x1024.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02868-2048x1365.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>The event reminded me a bit of the <strong>Microsoft MVP Summit</strong>, but with even closer access to the Docker product teams across multiple areas. Every year, the Captain Summit takes place in a different location, bringing together Docker staff from product groups, community management, marketing, and DevRel, along with fellow Docker Captains from around the world.</p>



<p>At the summit, we got an inside look at Docker’s roadmap and were among the first to learn about upcoming products and initiatives. We also had the opportunity to provide <strong>direct feedback</strong> to the product teams, helping shape the future of Docker from the community’s perspective.</p>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="683" src="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-1024x683.jpg" alt="" class="wp-image-9434" style="width:513px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-1024x683.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-300x200.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-768x512.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-1536x1024.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02780-2048x1365.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="819" height="1024" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-819x1024.jpg" alt="" class="wp-image-9423" style="width:295px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-819x1024.jpg 819w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-240x300.jpg 240w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-768x960.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-1229x1536.jpg 1229w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-1638x2048.jpg 1638w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_172318-scaled.jpg 2048w" sizes="auto, (max-width: 819px) 100vw, 819px" /></figure>



<p></p>



<p>This year’s summit was held in <strong>Istanbul</strong>, and it was a fantastic few days of connecting with so many brilliant people. I finally met in person several Docker staff members and Captains I’ve been collaborating with online. It was also a chance to reunite with friends from <strong>Microsoft</strong> and the <strong>MVP community</strong>.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-1024x768.jpg" alt="" class="wp-image-9431" style="width:545px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090902-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="768" height="1024" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-768x1024.jpg" alt="" class="wp-image-9432" style="width:433px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-768x1024.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-225x300.jpg 225w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-1152x1536.jpg 1152w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-1536x2048.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_090909-scaled.jpg 1920w" sizes="auto, (max-width: 768px) 100vw, 768px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="683" src="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-1024x683.jpg" alt="" class="wp-image-9437" style="width:630px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-1024x683.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-300x200.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-768x512.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-1536x1024.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02826-2048x1365.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>Of course, not everything we discussed can be shared publicly because of NDAs, but I can tell you that we all walked away with some exciting insights and some awesome <strong>Docker swag</strong>.</p>



<span id="more-9422"></span>



<p>Big shoutout to the entire <strong>Docker team</strong> for putting together such a memorable event. </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-1024x768.jpg" alt="" class="wp-image-9424" style="width:558px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_095306-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<h3 class="wp-block-heading">Highlights from the Summit</h3>



<p>While I can’t share details about the product roadmap just yet, I’ll definitely be posting updates here, in future talks, and across other media channels as new features and functionality roll out.</p>



<p>We dove deep into <strong>Docker security</strong>, exploring topics like hardened images, secure supply chains, and other ways Docker is raising the bar for safe, scalable containerization.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-1024x768.jpg" alt="" class="wp-image-9425" style="width:505px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_100445-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>We also dove into Docker AI and got hands-on with <strong>Docker Cagent</strong>, running through some great demos and a workshop to see how it works in practice. Another highlight was exploring <strong>MCP (Managed Container Platform)</strong>, including the MCP catalog and toolkit.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-1024x768.jpg" alt="" class="wp-image-9426" style="width:582px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104422-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-1024x768.jpg" alt="" class="wp-image-9427" style="width:584px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_104857-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>One of the big announcements from the summit was <strong>Docker Sandbox</strong>, a brand-new product that’s now public.<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://docs.docker.com/ai/mcp-catalog-and-toolkit/sandboxes">Check it out here</a>.</p>



<p><br>In short, <strong>Sandboxes</strong> are isolated execution environments that allow developers to run code and applications securely without impacting the host system.</p>



<p>The summit also included fun breakout sessions with product teams, lightning talks, content creation challenges, and a few rounds of <strong>presentation karaoke</strong> (which were hilarious). </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-1024x768.jpg" alt="" class="wp-image-9441" style="width:563px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251028_113803-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>Let’s just say that <strong>PHP</strong> made an unexpected cameo, and yes, <strong>AWS US East</strong> going down also got a shoutout too.</p>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-1024x768.jpg" alt="" class="wp-image-9447" style="width:607px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091314-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-1024x768.jpg" alt="" class="wp-image-9442" style="width:607px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091503-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-1024x768.jpg" alt="" class="wp-image-9443" style="width:604px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091645-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-1024x768.jpg" alt="" class="wp-image-9445" style="width:607px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091533-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-1024x768.jpg" alt="" class="wp-image-9446" style="width:612px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_091618-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>A nice bonus was meeting one of the <strong>founders of <a href="https://opentofu.org/" target="_blank" rel="noreferrer noopener">OpenTofu</a></strong>:</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-1024x768.jpg" alt="" class="wp-image-9428" style="width:511px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_094842-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>Also reconnecting with some former <strong>Microsoft colleagues</strong>. It’s always great to see familiar faces in new settings.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="768" height="1024" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-768x1024.jpg" alt="" class="wp-image-9429" style="width:406px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-768x1024.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-225x300.jpg 225w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-1152x1536.jpg 1152w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-1536x2048.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_120439-scaled.jpg 1920w" sizes="auto, (max-width: 768px) 100vw, 768px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-1024x768.jpg" alt="" class="wp-image-9430" style="width:483px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_165959-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<h3 class="wp-block-heading">Final Thoughts</h3>



<p>Overall, the experience was <strong>incredible</strong>. The energy, collaboration, and innovation at the Docker Captain Summit reminded me why I love being part of this community. </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-1024x768.jpg" alt="" class="wp-image-9438" style="width:586px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/20251029_100817-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="683" src="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-1024x683.jpg" alt="" class="wp-image-9435" style="width:501px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-1024x683.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-300x200.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-768x512.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-1536x1024.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/DSC02943-2048x1365.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="768" src="https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-1024x768.jpg" alt="" class="wp-image-9436" style="width:504px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-1024x768.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-300x225.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-768x576.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-1536x1152.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/IMG_1880-2048x1536.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>I’m truly honored to represent as a Docker Captain and can’t wait to share more about what’s coming from Docker in the months ahead.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="543" src="https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped-1024x543.jpg" alt="" class="wp-image-9461" style="width:612px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped-1024x543.jpg 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped-300x159.jpg 300w, https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped-768x407.jpg 768w, https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped-1536x815.jpg 1536w, https://www.buchatech.com/wp-content/uploads/2025/10/All-Docker-Captains-Oct-2025-Cropped.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Presenting at Applied AI 2025 Conf</title>
		<link>https://www.buchatech.com/2025/10/presenting-at-applied-ai-2025-conf/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Mon, 20 Oct 2025 22:02:36 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[IT Career]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9404</guid>

					<description><![CDATA[I’m excited to announce that in a couple of weeks I’ll be speaking at the upcoming Applied AI Conference, an event bringing together innovators, researchers, and industry leaders who are shaping the future of Artificial Intelligence. The Applied AI Conference is all about actionable insights where ideas meet execution. I’m looking forward to sharing lessons ... <a title="Presenting at Applied AI 2025 Conf" class="read-more" href="https://www.buchatech.com/2025/10/presenting-at-applied-ai-2025-conf/" aria-label="Read more about Presenting at Applied AI 2025 Conf">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I’m excited to announce that in a couple of weeks I’ll be speaking at the upcoming <strong><a href="https://appliedaiconf.com/" target="_blank" rel="noreferrer noopener">Applied AI Conference</a></strong>, an event bringing together innovators, researchers, and industry leaders who are shaping the future of Artificial Intelligence.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="727" src="https://www.buchatech.com/wp-content/uploads/2025/10/image-8-1024x727.png" alt="" class="wp-image-9411" style="width:500px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/image-8-1024x727.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/image-8-300x213.png 300w, https://www.buchatech.com/wp-content/uploads/2025/10/image-8-768x545.png 768w, https://www.buchatech.com/wp-content/uploads/2025/10/image-8.png 1196w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>The Applied AI Conference is all about actionable insights where ideas meet execution. I’m looking forward to sharing lessons learned from my AI journey, hearing from other brilliant minds in the community, and connecting with attendees who are just as passionate about AI innovation.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="442" src="https://www.buchatech.com/wp-content/uploads/2025/10/image-7-1024x442.png" alt="" class="wp-image-9410" style="width:581px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/image-7-1024x442.png 1024w, https://www.buchatech.com/wp-content/uploads/2025/10/image-7-300x130.png 300w, https://www.buchatech.com/wp-content/uploads/2025/10/image-7-768x332.png 768w, https://www.buchatech.com/wp-content/uploads/2025/10/image-7.png 1274w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<h3 class="wp-block-heading">Why This Conference Matters</h3>



<p>The AI landscape is evolving fast, and events like the Applied AI Conference create space for meaningful conversations about what’s next. It’s not just about tools and models it’s about empowering people, teams, and organizations to make smarter, faster decisions with AI.</p>



<p>This year, I’ll be giving <strong>two sessions</strong>, one being a <strong>session</strong> and the other being a <strong>fireside chat</strong> with Mike Jackson. Here is more information about my sessions: </p>



<h3 class="wp-block-heading">My Sessions at Applied AI Conference</h3>



<h4 class="wp-block-heading">The Easiest Way to Run LLMs Locally: Meet Docker Model Runner</h4>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="772" height="373" src="https://www.buchatech.com/wp-content/uploads/2025/10/image-6.png" alt="" class="wp-image-9408" style="width:525px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/image-6.png 772w, https://www.buchatech.com/wp-content/uploads/2025/10/image-6-300x145.png 300w, https://www.buchatech.com/wp-content/uploads/2025/10/image-6-768x371.png 768w" sizes="auto, (max-width: 772px) 100vw, 772px" /></figure>



<p></p>



<p>Curious about running large language models (LLMs) on your own machine without wrestling with complicated setups? In this session, I’ll introduce <strong>Docker Model Runner</strong>, a new feature in Docker Desktop that makes it incredibly easy to run LLMs locally.</p>



<p>Whether you’re a developer experimenting with AI, building offline applications, or simply looking for more control over your models, this session will show you how to get started in minutes. We’ll explore real examples and walk through what makes Docker Model Runner such a powerful addition for anyone working with AI tools.</p>



<p>This is perfect for anyone who wants to move fast with local AI experimentation, without needing to manage complex infrastructure or cloud dependencies.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h4 class="wp-block-heading">Fireside Chat: Beyond the Hype – Practical AI Integration in Business</h4>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="579" height="283" src="https://www.buchatech.com/wp-content/uploads/2025/10/image-5.png" alt="" class="wp-image-9407" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/image-5.png 579w, https://www.buchatech.com/wp-content/uploads/2025/10/image-5-300x147.png 300w" sizes="auto, (max-width: 579px) 100vw, 579px" /></figure>



<p>In this fireside chat titled <strong>“Beyond the Hype: Practical AI Integration in Business,”</strong> I’ll join <strong>Mike Jackson</strong> for a moderated discussion focused on how organizations can effectively adopt AI in the real world.</p>



<p>We’ll move past buzzwords to talk about real challenges, lessons learned, and success stories from our jouerneys working with AI so far. I&#8217;ll be drawing from my experience as an enterprise cloud leader, Microsoft MVP, author, and startup advisor, I’ll share how companies can strategically approach AI adoption from proof of concept to production use.</p>



<p>If you’re interested in how AI can truly add business value (not just headlines), this conversation will offer insights you can take back to your organization.</p>



<p>I’m honored to be presenting here and can’t wait to connect with the broader AI and developer community during the event.</p>



<p>If you’re attending, I’d love to see you there.<br>Check out the full speaker lineup here: <a href="https://appliedaiconf.com/speaker-directory" target="_blank" rel="noreferrer noopener">appliedaiconf.com/speaker-directory</a></p>



<span id="more-9404"></span>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>It’s Been a Year – Microsoft MVP for the 11th Time!</title>
		<link>https://www.buchatech.com/2025/10/its-been-a-year-microsoft-mvp-for-the-11th-year/</link>
		
		<dc:creator><![CDATA[S Buchanan]]></dc:creator>
		<pubDate>Thu, 02 Oct 2025 07:21:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Azure Arc]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[IT Career]]></category>
		<category><![CDATA[Microsoft Products]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[AKS]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[MicrosoftMVP]]></category>
		<category><![CDATA[MVP]]></category>
		<guid isPermaLink="false">https://www.buchatech.com/?p=9395</guid>

					<description><![CDATA[What a ride this year has been. Back in May, my entire team was eliminated and I was laid off from Microsoft. Not long after, I was honored to be named a Docker Captain, and soon after that I landed a new role leading Azure and AKS at Jamf, helping run their SaaS products in ... <a title="It’s Been a Year – Microsoft MVP for the 11th Time!" class="read-more" href="https://www.buchatech.com/2025/10/its-been-a-year-microsoft-mvp-for-the-11th-year/" aria-label="Read more about It’s Been a Year – Microsoft MVP for the 11th Time!">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>What a ride this year has been. Back in May, my entire team was eliminated and I was laid off from Microsoft. Not long after, I was honored to be named a Docker Captain, and soon after that I landed a new role leading Azure and AKS at Jamf, helping run their SaaS products in the cloud. </p>



<p>And yesterday, I found out that I’ve been re-awarded as a Microsoft MVP! This marks my 11th year as an MVP, all in the span of just a few months of major ups and downs. After a short detour (just under four years) working at the mothership, I’m excited to be back in the MVP community.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="920" height="525" src="https://www.buchatech.com/wp-content/uploads/2025/10/image-4.png" alt="" class="wp-image-9396" style="width:671px;height:auto" srcset="https://www.buchatech.com/wp-content/uploads/2025/10/image-4.png 920w, https://www.buchatech.com/wp-content/uploads/2025/10/image-4-300x171.png 300w, https://www.buchatech.com/wp-content/uploads/2025/10/image-4-768x438.png 768w" sizes="auto, (max-width: 920px) 100vw, 920px" /></figure>



<p>I never take this recognition for granted. It’s an honor to return to the MVP ranks and continue contributing as a community champion in the worlds of Microsoft, Azure, Azure Kubernetes Service, AI, and Open Source.</p>



<p>To all the other MVPs who were renewed—and to the new awardees announced on October 1—congratulations!</p>



<p>Stay tuned! </p>



<span id="more-9395"></span>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
