Modules

You learned about components, expressions, and the configuration syntax in the previous sections. Now you’ll learn about modules, the organizational structure that lets you package and reuse Alloy configurations.

Modules enable powerful configuration management capabilities:

  1. Code reuse: Package common pipeline patterns into reusable custom components.
  2. Organization: Structure complex configurations into maintainable units.
  3. Sharing: Distribute configurations across teams and projects.
  4. Composition: Combine multiple modules to build sophisticated data collection systems.

A module is a unit of Alloy configuration that contains components, custom component definitions, and import statements. The module you pass to the run command becomes the main configuration that Alloy executes.

You can import modules to reuse custom components defined by that module.

Import modules

You can import a module to use its custom components in other modules. The component controller processes import statements during configuration loading to make custom components available in the importing module’s namespace.

Import modules from multiple locations using one of the import configuration blocks:

  1. import.file: Imports a module from a file on disk.
  2. import.git: Imports a module from a file in a Git repository.
  3. import.http: Imports a module from an HTTP request response.
  4. import.string: Imports a module from a string.

Warning

You can’t import a module that contains top-level blocks other than declare or import.

You import modules into a namespace. This exposes the top-level custom components of the imported module to the importing module. The label of the import block specifies the namespace of an import.

For example, if a configuration contains a block called import.file "my_module", then custom components defined by that module appear as my_module.CUSTOM_COMPONENT_NAME. Namespaces for imports must be unique within a given importing module.

Namespace collision behavior

The component controller handles namespace collisions with specific rules:

Built-in component shadowing: If an import namespace matches the name of a built-in component namespace, such as prometheus, the built-in namespace becomes hidden from the importing module. Only components defined in the imported module are available.

Component shadowing warning: If you use a label for an import or declare block that matches a component, the component becomes shadowed and unavailable in your configuration.

Warning

If you use a label for an import or declare block that matches a component, the component becomes shadowed and unavailable in your configuration. For example, if you use the label import.file "mimir", you can’t use components starting with mimir, such as mimir.rules.kubernetes, because the label refers to the imported module.

Example

This example demonstrates how modules integrate with the component and expression concepts you learned earlier. The module defines a reusable log filtering component that showcases:

  1. Custom component definition: Using declare to create reusable logic.
  2. Arguments: Accepting configuration from component users.
  3. Exports: Exposing values for other components to reference.
  4. Internal pipeline: Using built-in components within the custom component.

Module definition helpers.alloy:

Alloy
declare "log_filter" {
  // argument.write_to is a required argument that specifies where filtered
  // log lines are sent.
  //
  // The value of the argument is retrieved in this file with
  // argument.write_to.value.
  argument "write_to" {
    optional = false
  }

  // loki.process.filter is our component which executes the filtering,
  // passing filtered logs to argument.write_to.value.
  loki.process "filter" {
    // Drop all debug- and info-level logs.
    stage.match {
      selector = `{job!=""} |~ "level=(debug|info)"`
      action   = "drop"
    }

    // Send processed logs to our argument.
    forward_to = argument.write_to.value
  }

  // export.filter_input exports a value to the module consumer.
  export "filter_input" {
    // Expose the receiver of loki.process so the module importer can send
    // logs to our loki.process component.
    value = loki.process.filter.receiver
  }
}

Use the module main.alloy:

Alloy
// Import our helpers.alloy module, exposing its custom components as
// helpers.COMPONENT_NAME.
import.file "helpers" {
  filename = "helpers.alloy"
}

loki.source.file "self" {
  targets = LOG_TARGETS

  // Forward collected logs to the input of our filter.
  forward_to = [helpers.log_filter.default.filter_input]
}

helpers.log_filter "default" {
  // Configure the filter to forward filtered logs to loki.write below.
  write_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url = LOKI_URL
  }
}

This example shows how:

  1. Modules encapsulate logic: The filtering logic is contained within the module.
  2. Arguments provide flexibility: The write_to argument lets users specify where filtered logs go.
  3. Exports enable connections: The filter_input export allows other components to send data to the filter.
  4. Component references work across modules: The main configuration references exports from the imported module using helpers.log_filter.default.filter_input.

Security

Since modules can load arbitrary configurations from potentially remote sources, you must carefully consider the security implications. The component controller executes all module content with the same privileges as the main Alloy process.

Best practices for secure module usage:

  1. Protect main configuration: Ensure attackers can’t modify the Alloy configuration files.
  2. Secure remote sources: Protect modules fetched from remote locations, such as Git repositories or HTTP servers.
  3. Validate module content: Review imported modules for malicious or unintended behavior.
  4. Use authentication: When fetching modules over HTTP or Git, use appropriate authentication mechanisms.
  5. Network security: Restrict network access for Alloy processes that load remote modules.
  6. File permissions: Set appropriate file system permissions for module files and directories.

Next steps

Now that you understand modules, explore advanced Alloy features:

For hands-on module development: