Azure Hub-and-Spoke Architecture Explained and Automated with OpenTofu

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.

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.

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.

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 https://opentofu.org.

You can access the GitHub Repository of my “OpenTofu Azure Hub and Spoke” solution here: https://github.com/Buchatech/OpenTofu-Azure-HubSpoke-public

Lets break down whats in the solution I built.


Solution Architecture

The solution deploys a production-style Azure network and platform foundation that includes:

  • Hub VNet with Azure Firewall, VPN Gateway, and DNS Private Resolver
  • Spoke VNet with peering and default routes through the firewall
  • Key Vault and Azure Container Registry using private endpoints
  • Optional Jumpbox VM for secure management access
  • GitHub Actions CI/CD pipeline using OIDC authentication

How the Automation Works

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

The bootstrap script creates everything you need to get started:

  1. It creates an Azure Storage Account to store your OpenTofu remote state.
  2. It generates a complete OpenTofu project, including modules, variables, and environment structure.
  3. It configures the backend so OpenTofu uses Azure Storage for state.
  4. It creates a ready-to-use GitHub Actions pipeline for CI/CD.

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.


Deployment Modes

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

FULL Mode (Default)
This is the enterprise-grade option.

  • Hub VNet with Azure Firewall, VPN Gateway, and DNS Private Resolver
  • Spoke VNet with peering and default route through the firewall
  • Private endpoints for Key Vault and Azure Container Registry
  • Optional Jumpbox VM for secure management
  • GitHub Actions CI/CD pipeline with OIDC authentication

BASIC Mode
This is a simpler version for learning or labs.

  • Hub VNet with Azure Firewall only
  • Spoke VNet with peering and default route through the firewall
  • Public access for Key Vault and Azure Container Registry
  • No Jumpbox, VPN Gateway, or DNS Private Resolver
  • GitHub Actions CI/CD pipeline with OIDC authentication

What the bootstrap.sh Script Does

When you run the bootstrap script, it will:

  1. Prompt you to select FULL or BASIC deployment mode
  2. Create an Azure Storage Account for OpenTofu remote state in rg-tfstate
  3. Generate the full OpenTofu repository structure based on your choice
  4. Configure the OpenTofu backend to use the storage account
  5. Create GitHub Actions workflow files for CI/CD
  6. Output the storage account details and the GitHub secrets you need to configure

From there, you are ready to deploy and customize the script and OpenTofu based on your Azure hub-and-spoke environment entirely through code.

Here is the Readme from the repo. It goes even more in depth into my “OpenTofu Azure Hub and Spoke” solution. I hope you find it useful!

********************************************************************************

Azure Hub-Spoke with OpenTofu

Azure base network architecture solution

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


Architecture Overview

This solution deploys a hub-and-spoke network architecture (visual shows full-private deployment):

Enterprise-grade Azure network architecture lab environment with Site-to-Site VPN, Azure Firewall, DNS Private Resolver, and core services

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.

Architecture Overview

This lab deploys a hub-and-spoke network architecture following Azure best practices (visual shows full private deployment):

┌──────────────────────────────────────────────────────────────────────┐
│                            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        │
              └──────────────────────────────┘

Read more