Regex in Terraform, Ansible and Prometheus: Functions, Filters and Matchers
Regex in infrastructure code #
The syntax from the regular expressions tutorial is the same, but each tool has its own functions and its own flavor. This page shows the three you will meet most: Terraform and OpenTofu, Ansible and Prometheus.
Terraform #
Terraform uses the RE2 syntax (no lookahead, no backreferences).
regex(): extract #
regex(pattern, string) returns the match. If the pattern has no groups the result is a string, with groups it is a list, with named groups a map. It fails if there is no match.
locals {
arn = "arn:aws:s3:::my-bucket"
bucket = regex("arn:aws:s3:::(.+)", local.arn)[0] # "my-bucket"
parts = regex("^(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)", "1.9.5")
# { major = "1", minor = "9" }
}
output "minor" {
value = local.parts.minor
}
In HCL strings a backslash must be doubled (\\.), or use a heredoc. Test expressions in terraform console.
regexall(): find every match #
regexall returns a list, and an empty list when nothing matches, so it never fails:
locals {
has_prod = length(regexall("prod", var.environment)) > 0
numbers = regexall("[0-9]+", "web-12-db-7") # ["12", "7"]
}
replace(): substitute with regex #
If the second argument is wrapped in /.../ it is a regex, and $1 refers to a group:
locals {
safe_name = replace(lower(var.name), "/[^a-z0-9-]/", "-")
swapped = replace("2026-10-01", "/([0-9]{4})-([0-9]{2})-([0-9]{2})/", "$3/$2/$1") # 01/10/2026
}
For simple cases prefer startswith(), endswith() and strcontains(): they are clearer than a pattern.
Validate variables #
can() turns the error of regex() into false, which is what a validation condition needs:
variable "bucket_name" {
type = string
validation {
condition = can(regex("^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$", var.bucket_name))
error_message = "The name must be 3-63 characters of lowercase letters, numbers, dots and hyphens."
}
}
variable "ami_id" {
type = string
validation {
condition = can(regex("^ami-[0-9a-f]{8}([0-9a-f]{9})?$", var.ami_id))
error_message = "Use an AMI id such as ami-0123456789abcdef0."
}
}
See variables, outputs and locals and functions.
Ansible #
Ansible uses Python regex. There are filters and tests for variables, and modules for files.
- hosts: localhost
gather_facts: false
vars:
version_line: "nginx version: nginx/1.27.1"
tasks:
- name: Extract the version
ansible.builtin.debug:
msg: "{{ version_line | regex_search('[0-9]+\\.[0-9]+\\.[0-9]+') }}"
- name: Replace with groups
ansible.builtin.debug:
msg: "{{ '2026-10-01' | regex_replace('([0-9]{4})-([0-9]{2})-([0-9]{2})', '\\3/\\2/\\1') }}"
- name: Every number
ansible.builtin.debug:
msg: "{{ 'web-12-db-7' | regex_findall('[0-9]+') }}"
- name: Condition with a regex test
ansible.builtin.debug:
msg: "production host"
when: inventory_hostname is match('^prod-')regex_search,regex_replaceandregex_findallare filters.- Tests:
is match('...')anchors at the start,is search('...')looks anywhere,is regex('...')is a general test with options. - In double-quoted YAML a backslash is doubled; in single-quoted YAML it is not.
Change files with a pattern:
- name: Disable root login over SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
notify: Restart sshd
- name: Update the version in a file
ansible.builtin.replace:
path: /etc/myapp/app.conf
regexp: '^version=.*$'
replace: 'version=2.0'
lineinfile replaces the last line that matches regexp, or appends line if none does. replace changes every match. Both are idempotent, so they are safe to run repeatedly (see Install Ansible).
Prometheus #
Label matchers accept regex with =~ (match) and !~ (no match). The expression is fully anchored, as if it had ^(...)$, and uses RE2.
up{job=~"node|prometheus"} # one of two jobs
http_requests_total{status=~"5.."} # status codes 500-599
http_requests_total{path!~"/health|/metrics"} # exclude paths
node_filesystem_avail_bytes{mountpoint=~"/(var|home).*"}
Because of the implicit anchors, job=~"node" only matches the job called exactly node; to match a prefix write node.*. Regex is also used in relabel_configs to rewrite labels:
relabel_configs:
- source_labels: [__address__]
regex: '([^:]+):\d+'
target_label: host
replacement: '$1'
This stores the address without its port in the label host. See Prometheus with Docker and Grafana for the setup.
Quick comparison #
| Tool | Flavor | Extract | Replace | Anchored by default |
|---|---|---|---|---|
grep -E, sed -E |
POSIX ERE | grep -o |
s/…/…/ |
No |
| Terraform | RE2 | regex(), regexall() |
replace(s, "/…/", "$1") |
No |
| Ansible | Python | regex_search |
regex_replace |
match yes, search no |
| Prometheus | RE2 | =~ matcher |
relabel_configs |
Yes |