Module
The module resource allows you to reuse and organize HCL configuration by importing external configuration files. Modules enable code reusability, maintainability, and collaboration by packaging common lab patterns into reusable components that can be shared across different lab configurations.
Use Cases
Section titled “Use Cases”As a lab author, you can use modules to create more organized and reusable configurations:
- Configuration Reusability: Package common lab patterns (e.g., LAMP stack, Kubernetes cluster) into modules that can be reused across multiple labs
- Team Collaboration: Share standardized lab components with team members through module registries or repositories
- Complex Lab Organization: Break large lab configurations into smaller, manageable modules organized by functionality
- Environment Variations: Create modules that can be configured with different variables for development, staging, and production environments
- Third-Party Integration: Use community modules or vendor-provided modules for common infrastructure patterns
- Version Management: Use different versions of modules to maintain compatibility and control updates
Modules promote code reuse and help maintain consistency across lab configurations while reducing duplication and complexity.
HCL Syntax
Section titled “HCL Syntax”Basic Syntax
Section titled “Basic Syntax”module "name" {  source = "./path/to/module"}Full Syntax
Section titled “Full Syntax”module "web_stack" {  source  = "github.com/example/lab-modules//web-stack"  version = "v1.2.0"
  variables = {    environment = "production"    cpu_limit   = 2048    replicas    = 3  }
  depends_on = ["resource.network.main"]}Fields
Section titled “Fields”| Field | Required | Type | Description | 
|---|---|---|---|
| source | ✓ | string | Location of the module (local path, GitHub URL, or registry reference) | 
| version | string | Version of the module to use. Defaults to “latest”. | |
| variables | any | Variables to pass to the module (can be object, list, or primitive values) | |
| depends_ | list(string) | List of resource dependencies (inherited from ResourceBase) | |
| disabled | bool | Whether this module is disabled. Defaults to false. | 
Module Sources
Section titled “Module Sources”Modules can be sourced from different locations:
| Source Type | Format | Example | 
|---|---|---|
| Local Filesystem | Relative path | source = "./modules/web-server" | 
| GitHub Repository | GitHub URL with optional subdirectory | source = "github.com/org/repo//modules/web" | 
| Module Registry | registry/namespace/module format | source = "instruqt.com/hashicorp/consul" | 
Module Reference
Section titled “Module Reference”Resources within modules are referenced using the syntax:
module.module_name.resource.resource_type.resource_nameModule outputs are referenced using:
module.module_name.output.output_nameExamples
Section titled “Examples”Local Module
Section titled “Local Module”module "database" {  source = "./modules/postgres"
  variables = {    database_name = "myapp"    username      = "admin"    port         = 5432  }}
# Reference module resourcesresource "container" "app" {  image {    name = "myapp:latest"  }
  environment = {    DB_HOST = module.database.resource.container.postgres.meta.name    DB_PORT = "5432"  }}Remote GitHub Module
Section titled “Remote GitHub Module”module "k8s_cluster" {  source  = "github.com/instruqt/lab-modules//kubernetes/basic-cluster"  version = "v2.1.0"
  variables = {    cluster_name = "demo-cluster"    node_count   = 3    cpu_per_node = 2048  }}
# Use module outputsoutput "cluster_kubeconfig" {  value = module.k8s_cluster.output.kubeconfig_path}Module Registry Source
Section titled “Module Registry Source”module "web_stack" {  source = "instruqt.com/examples/lamp-stack"
  variables = {    environment     = variable.env    php_version     = "8.1"    mysql_root_pass = "secure_password"  }}Complex Module with Dependencies
Section titled “Complex Module with Dependencies”# Base networking modulemodule "network" {  source = "./modules/network"
  variables = {    subnet_cidr = "10.0.0.0/16"  }}
# Web tier module that depends on networkmodule "web_tier" {  source = "./modules/web-tier"
  depends_on = ["module.network"]
  variables = {    network_id    = module.network.output.network_id    instance_type = "standard"    replica_count = 2  }}
# Database tier modulemodule "db_tier" {  source = "./modules/database"
  depends_on = ["module.network"]
  variables = {    network_id     = module.network.output.network_id    database_size  = "large"    backup_enabled = true  }}Module with Different Variable Types
Section titled “Module with Different Variable Types”module "microservices" {  source = "./modules/microservices"
  variables = {    # String variables    environment = "staging"    namespace   = "my-app"
    # Number variables    cpu_limit    = 1024    memory_limit = 2048
    # Boolean variables    monitoring_enabled = true    debug_mode        = false
    # List variables    services = ["api", "web", "worker"]    ports    = [8080, 8081, 8082]
    # Object variables    database_config = {      host     = "postgres.local"      port     = 5432      database = "myapp"      ssl      = true    }
    # Complex nested structure    service_configs = {      api = {        replicas = 3        cpu      = 512        memory   = 1024      }      web = {        replicas = 2        cpu      = 256        memory   = 512      }    }  }}Conditional Module Usage
Section titled “Conditional Module Usage”module "monitoring" {  source = "./modules/monitoring"
  disabled = !variable.enable_monitoring
  variables = {    environment = variable.environment    services    = ["web", "api", "database"]  }}Module Chaining
Section titled “Module Chaining”# Base infrastructure modulemodule "infrastructure" {  source = "./modules/infrastructure"
  variables = {    environment = variable.environment  }}
# Application module using infrastructure outputsmodule "application" {  source = "./modules/application"
  depends_on = ["module.infrastructure"]
  variables = {    network_id = module.infrastructure.output.network_id    subnet_id  = module.infrastructure.output.subnet_id    app_name   = variable.app_name  }}
# Monitoring module using both previous modulesmodule "monitoring" {  source = "./modules/monitoring"
  depends_on = ["module.infrastructure", "module.application"]
  variables = {    network_id      = module.infrastructure.output.network_id    app_container   = module.application.output.app_container_name    monitoring_port = 9090  }}Module Structure
Section titled “Module Structure”A typical module directory structure:
modules/└── web-server/    ├── main.hcl          # Main module configuration    ├── variables.hcl     # Variable definitions    ├── outputs.hcl       # Output definitions    └── templates/        # Template files (if needed)        └── nginx.conf.tplExample Module Implementation
Section titled “Example Module Implementation”modules/web-server/variables.hcl:
variable "server_name" {  default     = "web-server"  description = "Name of the web server"}
variable "port" {  default     = 80  description = "Port for the web server"}
variable "image_tag" {  default     = "latest"  description = "Docker image tag to use"}modules/web-server/main.hcl:
resource "container" "web" {  image {    name = "nginx:${variable.image_tag}"  }
  container_name = variable.server_name
  port {    local  = variable.port    remote = 80  }}
resource "template" "config" {  source      = "templates/nginx.conf.tpl"  destination = "./nginx.conf"
  vars = {    server_name = variable.server_name    listen_port = variable.port  }}modules/web-server/outputs.hcl:
output "container_name" {  value       = resource.container.web.meta.name  description = "Name of the web server container"}
output "service_url" {  value       = format("http://localhost:%d", variable.port)  description = "URL to access the web server"}Best Practices
Section titled “Best Practices”- Clear Interface: Define clear input variables and outputs for your modules
- Documentation: Include descriptions for all variables and outputs
- Version Control: Use version tags for modules shared across teams
- Default Values: Provide sensible defaults for optional variables
- Modular Design: Keep modules focused on specific functionality
- Dependency Management: Use explicit dependencies when modules need specific ordering
Common Patterns
Section titled “Common Patterns”Environment-Specific Modules
Section titled “Environment-Specific Modules”module "app_environment" {  source = "./modules/environment"
  variables = {    environment = variable.environment    config = {      dev = {        cpu       = 512        memory    = 1024        replicas  = 1        debug     = true      }      prod = {        cpu       = 2048        memory    = 4096        replicas  = 3        debug     = false      }    }[variable.environment]  }}Shared Service Modules
Section titled “Shared Service Modules”# Shared database modulemodule "shared_database" {  source = "./modules/postgres"
  variables = {    instance_type = "high-performance"    databases = ["app1", "app2", "app3"]  }}
# Multiple apps using the shared databasemodule "app1" {  source = "./modules/web-app"
  variables = {    database_host = module.shared_database.output.host    database_name = "app1"  }}
module "app2" {  source = "./modules/web-app"
  variables = {    database_host = module.shared_database.output.host    database_name = "app2"  }}Validation Rules
Section titled “Validation Rules”- Source Required: The sourcefield must be specified
- Valid Module Name: Module names must follow valid identifier rules
- Dependency References: Dependencies in depends_onmust reference existing resources
- Variable Types: Variables passed to modules must match expected types in the module
- Single Label: Module blocks must have exactly one label (the module name)
Modules provide a powerful way to organize, reuse, and share lab configuration patterns, enabling you to build complex lab environments from well-tested, reusable components.
