1. Configuration blocks #
Attributes, blocks and expressions in River language.
Understanding the syntax #
Your alloy config is written in a file (usually config.alloy) using a declarative syntax strongly inspired by Terraform. In this alloy configuration, a component is defined by a name and an identifier. Inside the block, you define attributes (e.g., url = "http://...") and potentially sub-blocks. Components export values that can be referenced by other components (like alloy prometheus or prometheus.exporter.self), creating the pipeline.
prometheus.scrape "default" {
targets = [{"__address__" = "localhost:9090"}]
forward_to = [prometheus.remote_write.mimir.receiver]
}Comma management #
In River, comma management in arrays and definitions is very flexible. It is recommended to add a comma after the last element of a list or multi-line configuration (trailing comma). This makes it easier to add subsequent elements and significantly reduces the risk of conflicts during code merges (Git merges).
prometheus.exporter.unix "example" {
enable_collectors = [
"cpu",
"meminfo",
"diskstats",
"textfile",
]
textfile {
directory = "/var/lib/node_exporter/textfile_collector"
}
}2. River vs HCL #
Differences and specifics of the River language compared to HCL.
Inspiration and operation #
Although alloy river is largely inspired by the HCL (HashiCorp Configuration Language) used by Terraform (similar syntax with blocks and attributes), it is specifically designed for continuous observability. Unlike HCL, which is often used for static infrastructure deployments (evaluated once during an apply), River maintains a dynamic directed acyclic graph (DAG). When a variable or secret is modified, River automatically and continuously re-evaluates all dependent components, without requiring a restart.
3. Variables and Functions #
Using logic to make configurations dynamic.
Making the configuration dynamic #
The River language allows you to use expressions to make your configuration dynamic. You can call built-in functions (like sys.env("VAR_NAME") to read environment variables, or string manipulation functions) and define local configuration blocks (local.file) to reuse values, thus avoiding repetition and facilitating maintenance.
4. Secrets Management #
Integrating environment variables and vault files.
Securing access #
It is crucial not to store passwords or tokens in plain text in your configuration files. Alloy offers several methods to manage secrets: using the sys.env() function, reading from secure files on disk, or integrating with third-party secret management solutions via specific components. Values marked as secret are never displayed in plain text in logs or the UI.
Best Practice: Use the local.file component to read dynamically mounted secrets (like Kubernetes or Vault secrets) so that Alloy reacts automatically if the secret file changes without restarting the process.Common Mistake: Hardcoding API tokens in the configuration file. This exposes the secret in your Git repository and in plain text in Alloy's UI (on port 12345).
local.file "mimir_api_key" {
filename = "/etc/alloy/secrets/mimir_token.txt"
is_secret = true
}
prometheus.remote_write "cloud" {
endpoint {
url = "https://mimir-url/api/v1/push"
basic_auth {
username = "admin"
password = local.file.mimir_api_key.content
}
}
}Here is also an example of direct integration with HashiCorp Vault using the remote.vault component:
remote.vault "api_token" {
server = "https://vault.internal:8200"
path = "secret/data/alloy/api"
auth {
token = sys.env("VAULT_TOKEN")
}
}
prometheus.remote_write "cloud" {
endpoint {
url = "https://mimir-url/api/v1/push"
basic_auth {
username = "admin"
password = remote.vault.api_token.data["password"]
}
}
}5. Alloy Observability #
Monitor the health of your own pipelines via the native UI interface.
The local user interface #
Alloy embeds an HTTP server (via alloy port 12345 by default) that exposes the grafana alloy ui, a very useful graphical interface. This UI allows you to visualize the graph (DAG) of your components, check their state (Healthy/Unhealthy), and inspect the evaluated arguments and exports of each component in real-time. It is the essential tool for alloy live debugging of your configurations.
