# Regex in Terraform, Ansible and Prometheus: Functions, Filters and Matchers

> Use regular expressions in Terraform regex(), regexall() and replace(), in variable validation, in Ansible regex_search and lineinfile, and in Prometheus label matchers.

- Source: https://www.itwonderlab.com/regex-terraform-ansible-prometheus/
- Published: 2026-09-13
- Updated: 2026-09-13
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Regex in infrastructure code

The syntax from the [regular expressions tutorial](https://www.itwonderlab.com/regex-tutorial-examples/) is the same, but each tool has its own functions and its own flavor. This page shows the three you will meet most: [Terraform](https://www.itwonderlab.com/terraform/) and [OpenTofu](https://www.itwonderlab.com/opentofu/), [Ansible](https://www.itwonderlab.com/ansible/) and [Prometheus](https://www.itwonderlab.com/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.**

```hcl
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:

```hcl
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:

```hcl
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:

```hcl
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](https://www.itwonderlab.com/terraform-variables-outputs-locals/) and [functions](https://www.itwonderlab.com/terraform-functions/).

## Ansible

Ansible uses **Python** regex. There are filters and tests for variables, and modules for files.

```yaml title="regex.yml"
- 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_replace` and `regex_findall` are 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:

```yaml
- 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](https://www.itwonderlab.com/install-ansible-first-playbook/)).

## Prometheus

Label matchers accept regex with `=~` (match) and `!~` (no match). The expression is **fully anchored**, as if it had `^(...)$`, and uses RE2.

```text
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:

```yaml
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](https://www.itwonderlab.com/install-prometheus-docker/) and [Grafana](https://www.itwonderlab.com/install-grafana-docker/) 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** |
