Skip to main content

Set Up a Module

Last updated on

Harness reads your module's files and automatically parses them into tabs in the Module Registry. Structure your repository correctly so the registry can surface your module's inputs, outputs, dependencies, resources, documentation, and submodules.

This topic covers the required module structure, standard files, submodules, and repository layouts.


What you will learn

By the end of this page, you will understand:

  • Required files: Which files (main.tf, variables.tf, outputs.tf, versions.tf, README.md) are required or recommended, and what each one does.
  • Registry tab mapping: How each standard file maps to a tab in the Module Registry (Inputs, Outputs, Dependencies, Resources, Readme, Submodules).
  • Submodule structure: How to add reusable child modules and how the registry parses them one level deep.
  • Non-standard layouts: How to configure a Folder Path when your module does not sit at the repository root, such as in a monorepo.

Module requirements

A module has a root module, which contains the top-level configuration for the module. The root module is required. Reusable child modules (submodules) are optional. The most common entry-point file is main.tf.


Module layout

A typical module repository contains the following files and directories:

module-name/
├── main.tf # Primary resource configurations
├── variables.tf # Input variable definitions
├── outputs.tf # Output values
├── README.md # Documentation for the module
├── versions.tf # Version constraints and provider requirements
├── provider.tf # Provider configuration (optional)
├── modules/ # Nested submodules (optional)
├── examples/ # Usage examples (for integration testing)
└── tests/ # Automated test files (for Tofu/Terraform testing)

Standard files and registry tabs

The Module Registry parses standard files and surfaces their contents in corresponding tabs. If something looks incorrect in the registry, update the source files rather than the registry.

FilePurposeRegistry tab
variables.tfDeclares the module's input variables. Include descriptions and, when applicable, default values.Inputs
outputs.tfDeclares the values the module returns, so users can access relevant module data.Outputs
versions.tfDeclares required providers and version constraints to keep the module stable.Dependencies
main.tfDeclares the resources the module manages.Resources
README.mdHuman-readable documentation: overview, usage, and examples.Readme
modules/Folder of nested submodules. Metadata is collected one level deep.Submodules
provider.tfProvider configuration. Define settings here rather than hardcoding them in the module so consumers retain flexibility.

The examples/ and tests/ directories support module testing but do not appear as registry tabs. Go to Test a Module to configure module testing.

Example: variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
Example: outputs.tf
output "instance_id" {
description = "ID of the created EC2 instance"
value = aws_instance.example.id
}
Example: versions.tf
tofu {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}

Submodules

Submodules let you ship smaller reusable pieces alongside a root module. Place them in a folder named modules/ at the root. Submodules are only recognized inside that folder.

Submodules cannot carry their own version

Git tags apply only to the root module, so submodules inherit the root module's tag. Do not add a version argument to a submodule source that uses the // path syntax. OpenTofu and Terraform reject this source format.

The registry parses only the immediate children of the modules/ directory. Deeper submodules remain usable through the // subpath syntax but do not appear in the registry tabs.

Example repository tree with submodules:

.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ ├── submoduleA/
│ │ ├── main.tf
│ │ └── ...
│ └── submoduleB/
│ ├── README.md
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf

Go to Explore Module Details to review how submodule metadata surfaces in the registry.


Non-standard layouts

By default, the registry expects the root module (main.tf) at the repository root and submodules inside a modules/ folder. If your module does not sit at the repository root, for example in a monorepo holding multiple modules, set the Folder Path field when you register it. Folder Path maps to the repository_path argument on the harness_platform_infra_module Terraform resource.

Setting Folder Path re-roots all resolution, including where the registry looks for the modules/ folder, so submodules are discovered relative to the path you set.

Example: Root module lives in terraform/infrastructure:

.
├── README.md
└── terraform/
└── infrastructure/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
└── submoduleA/
└── main.tf

Set Folder Path during registration:

  1. In the New Module wizard, configure the module name, provider, connector, and repository.
  2. In the Folder Path field, enter the path from the repository root to the directory containing the root module, for example terraform/infrastructure.
  3. Complete the remaining fields and select Save.

Or set the equivalent repository_path argument in Terraform:

resource "harness_platform_infra_module" "example" {
name = "my-module"
system = "aws"
repository = "tf-aws-vpc"
repository_branch = "main"
repository_path = "terraform/infrastructure"
repository_connector = "account.mygithub"
}
Submodules not appearing?

Confirm that Folder Path points at the directory containing main.tf and that submodules sit in a modules/ folder directly under that path. Metadata collection is one level deep, so deeply nested submodules are not surfaced even though they remain usable through the // subpath syntax.


  • Root module: Keep the root module at the repository root or configured Folder Path.
  • Submodules: Place reusable child modules in the modules/ directory.
  • Documentation: Include module usage and examples in README.md.
  • Version constraints: Specify compatible OpenTofu or Terraform and provider versions in versions.tf.
  • Testing: Store module tests in the tests/ directory.

Next steps

Once your repository is ready, go to Register a Module to add the module to the registry.