---
title: Operating a Terraform module service catalog (Part 1)
description: First in a series of posts showing how we operate a service catalog for Terraform modules - infrastructure as code components - on GitHub.
image: https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/lego-bricks.webp
---

[![Frontier logo with white text](https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/frontier-logo-horizontal-white.svg)](https://frontierhq.com)

- [KUBERNETES](https://frontierhq.com/kubernetes)
- [DATA & AI](https://frontierhq.com/data-ai)
- [CLOUD](https://frontierhq.com/cloud)
- [AUTOMATION](https://frontierhq.com/automation)
- [PRODUCTS](https://frontierhq.com/products)
- ABOUT US
  
    - [PARTNERS](https://frontierhq.com/partners)
    - [WHO WE ARE](https://frontierhq.com/who-we-are)
    - [THOUGHT LEADERSHIP](https://frontierhq.com/blog)

[Let's Chat](https://frontierhq.com/lets-chat)

Technical

# Operating a Terraform module service catalog (Part 1)

First in a series of posts showing how we operate a service catalog for Terraform modules - infrastructure as code components - on GitHub.

[Fraser](https://frontierhq.com/blog/author/fraser)

 Sep 9, 2024

<http://www.facebook.com/share.php?u=https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=facebook> <http://www.linkedin.com/shareArticle?mini=true&url=https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=linkedin> <https://twitter.com/intent/tweet?original_referer=https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=twitter&url=https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=twitter&source=tweetbutton&text=> [mailto:?subject=Check%20out%20https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=email%20&body=Check%20out%20https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=email](mailto:?subject=Check%20out%20https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=email%20&body=Check%20out%20https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1&utm_medium=social&utm_source=email)

---

Standardisation, efficiency and developer experience sit front and centre for us when helping customers to operate predictably and innovate safely. And it's the same when running our own platform. Service catalogs - curated collections of re-useable, versioned and supported components that meet organisational standards - are a step in the right direction.

In this series of posts, I'll show you how we operate a service catalog for Terraform modules - infrastructure as code components - on GitHub.

1. Why Terraform modules? (this post)
2. Using GitHub + Vertag for source control and versioning
3. Publishing modules for use in Terraform with GitHub releases
4. Generating and consuming the service catalog

## Why Terraform modules?

> A module is a container for multiple resources that are used together. You can use modules to create lightweight abstractions, so that you can describe your infrastructure in terms of its architecture, rather than directly in terms of physical objects.
> 
> [https://developer.hashicorp.com/terraform/language/modules/develop](https://developer.hashicorp.com/terraform/language/modules/develop)

At Frontier, we typically use [Terraform modules](https://developer.hashicorp.com/terraform/language/modules/develop) to deploy and manage cloud infrastructure on Microsoft Azure, Amazon Web Services (AWS) and Google Cloud Platform (GCP). The modules we build let us do useful things like:

- apply a common naming/tagging convention
- set safe defaults around security, cost, scaling and performance
- start with a "known good" configuration

Each module includes one or more resources and require a common set of input variables - like zone, environment, location and identifier - as well as some resource specific ones, too.

Here's an example - a module that deploys an [Azure Kubernetes Service](https://azure.microsoft.com/en-us/products/kubernetes-service) cluster:

```
# variables.tf

variable "environment" {
  type = string
}

variable "identifier" {
  type = string
}

variable "location" {
  type = string
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "vm_size" {
  type    = string
  default = "Standard_B4ms"
}

variable "zone" {
  type = string
}

...
```

```
# locals.tf

locals {
  kubernetes_version = "1.30.1"

  tags = {
    Environment   = var.environment
    Location      = var.location
    ModuleName    = "kubernetes-cluster"
    ModuleVersion = "1.0.9"
    Zone          = var.zone
  }
}
```

```
# resources.tf

resource "azurerm_kubernetes_cluster" "main" {
  name                = "k8s-${var.zone}-${var.environment}-${var.location}-${var.identifier}"
  location            = var.location
  resource_group_name = var.resource_group_name
  
  azure_policy_enabled = true
  kubernetes_version   = local.kubernetes_version
  
  ...
  
  azure_active_directory_role_based_access_control {
    managed                = true
    admin_group_object_ids = var.admin_group_object_ids
    azure_rbac_enabled     = true
  }
  
  ...

  tags = merge(var.tags, local.tags)
}

...
```

Full code [here](https://github.com/frontierhq/azurerm-terraform-modules/tree/main/modules/kubernetes-cluster/src).

The Kubernetes cluster this module deploys:

- has a naming/tagging convention that follows the organisational standard
- uses a version of Kubernetes that's supported by the platform team
- integrates with Azure Policy to automatically apply governance controls
- integrates with Microsoft Entra ID (Azure Active Directory) for role based access control

When engineers or developers use this module to deploy an [Azure Kubernetes Service](https://azure.microsoft.com/en-us/products/kubernetes-service) cluster to our platform, they're probably going to get to production faster, safer and cheaper than if they'd have written Terraform from scratch because it meets organisational standards "out the box".

It's worth calling out that Terraform documentation says:

> We do not recommend writing modules that are just thin wrappers around single other resource types. If you have trouble finding a name for your module that isn't the same as the main resource type inside it, that may be a sign that your module is not creating any new abstraction and so the module is adding unnecessary complexity. Just use the resource type directly in the calling module instead.
> 
> [https://developer.hashicorp.com/terraform/language/modules/develop#when-to-write-a-module](https://developer.hashicorp.com/terraform/language/modules/develop#when-to-write-a-module)

I understand the intent with this recommendation, and I agree that if a module is used as a simple wrapper - passing inputs straight through - then it's probably adding unnecessary complexity. In our example however - despite only defining a single resource - the module is reducing cognitive load, improving quality and consistency, and shortening release cycles. That's not unnecessary complexity, that's a step towards a [golden path](https://platformengineering.org/blog/decoding-golden-paths-the-highway-for-your-developers).

In the next post, I'll talk about how we use GitHub to store our Terraform modules, and how we solved the problem of independently versioning modules in a single repository with a tool called Vertag.

[Technical](https://frontierhq.com/blog/tag/technical)

## Similar posts

<https://frontierhq.com/blog/autoscaling-kubernetes-workloads-with-custom-schedules>

Technical

### [Autoscaling Kubernetes workloads with custom schedules](https://frontierhq.com/blog/autoscaling-kubernetes-workloads-with-custom-schedules)

Would you like to know how we cut annual cloud costs by over 45% for one of our major financial services clients? Read on!

 Anthony  Aug 3, 2023

<https://frontierhq.com/blog/secrets-as-code-with-mozilla-sops>

Technical

### [Secrets as code with Mozilla SOPS](https://frontierhq.com/blog/secrets-as-code-with-mozilla-sops)

Secrets can be difficult to maintain in a controlled and secure manner. Who should see the secret before it is stored? Where do you store the secret...

 Anthony  Mar 1, 2023

<https://frontierhq.com/blog/azure-devops-pipelines-with-multiple-schedules>

Technical

### [Azure DevOps Pipelines with multiple schedules](https://frontierhq.com/blog/azure-devops-pipelines-with-multiple-schedules)

The client wants an Azure DevOps native solution, and everything is deployed by ADO pipelines.

 Neil  Feb 22, 2023

[![Frontier logo with white text](https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/frontier-logo-horizontal-white.svg)](https://frontierhq.com)

Centrum House  
38 Queen Street  
Glasgow G1 3DX

### Products

- [Sheriff](https://frontierhq.com/products/sheriff)
- [Governor](https://frontierhq.com/products)
- [Wrangler](https://frontierhq.com/products)
- [Ranger](https://frontierhq.com/products)

### Solutions

- [Cloud landing zones](https://frontierhq.com/solutions)
- [Container platforms](https://frontierhq.com/solutions)
- [Deployment automation](https://frontierhq.com/solutions)
- [Security automation](https://frontierhq.com/solutions)

### Company

- [Who we are](https://frontierhq.com/who-we-are)
- [Thought leadership](https://frontierhq.com/blog)
- [Contact us](https://frontierhq.com/lets-chat)
- [Terms](https://frontierhq.com/terms-and-conditions)

![Microsoft Partner logo](https://22238495.fs1.hubspotusercontent-na1.net/hub/22238495/hubfs/SolutionsPartner-png-1.png?width=163&height=100&name=SolutionsPartner-png-1.png) ![Kubernetes Certified Service Provider logo](https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/kubernetes-kcsp-color-1.svg) ![Elastic Partner logo](https://22238495.fs1.hubspotusercontent-na1.net/hub/22238495/hubfs/partner-badge.png?width=100&height=100&name=partner-badge.png)![SUSE Emerald Partner logo](https://22238495.fs1.hubspotusercontent-na1.net/hub/22238495/hubfs/Sell%20Emerald%20Partner.png?width=100&height=100&name=Sell%20Emerald%20Partner.png)![Cyber Essentials logo](https://22238495.fs1.hubspotusercontent-na1.net/hub/22238495/hubfs/Cyber-Essentials-Logo-v2.png?width=84&height=100&name=Cyber-Essentials-Logo-v2.png) ![Living Wage Employer logo](https://22238495.fs1.hubspotusercontent-na1.net/hub/22238495/hubfs/living-wage-employer-logo.png?width=127&height=100&name=living-wage-employer-logo.png) ![Great Place To Work logo](https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/great-place-to-work-logo.svg)

© 2026 Frontier

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Fraser",
    "url" : "https://frontierhq.com/blog/author/fraser"
  },
  "dateModified" : "2024-12-04T16:27:02.951Z",
  "datePublished" : "2024-09-09T10:43:25.000Z",
  "headline" : "Operating a Terraform module service catalog (Part 1)",
  "image" : [ "https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/lego-bricks.webp" ],
  "mainEntityOfPage" : {
    "@id" : "https://frontierhq.com/blog/operating-a-terraform-module-service-catalog-part-1",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://22238495.fs1.hubspotusercontent-na1.net/hubfs/22238495/RGB_Frontier_Logo_Colour+Grey.png"
    },
    "name" : "Frontier"
  }
}
```