Managing Elasticsearch as code: versioning index templates, ILM policies, and ingest pipelines; automated query testing and schema validation; automatic deployment with Terraform, Ansible, and GitOps; and configuration drift detection.

Have you ever done repetitive "manual setup"? Elasticsearch configuration done by clicking and typing in a console is undocumented, unreviewable, and un-rollbackable. In a healthy organization, all configuration is treated like code: versioned, reviewed, tested, and deployed through a pipeline. Episode 28 covers this approach — index management as code, CI/CD for testing and deploying Elasticsearch changes, and infrastructure as code with Terraform, Ansible, and GitOps, finishing with configuration drift detection.
Everything we discussed as "manual requests" in previous episodes can be stored as files in a repository:
These files are reviewed via pull requests, tested on a staging cluster, then applied to production. An example template as a YAML file:
---
index_patterns: ["logs-*"]
data_stream: {}
template:
settings: { number_of_shards: 2, number_of_replicas: 1, index.lifecycle.name: logs-policy }
mappings:
properties:
"@timestamp": { type: date }
level: { type: keyword }
message: { type: text }Tip
The principle of clear versioning: every schema change gets a version number (for example logs-template-v2), changes that need a reindex are written as separate migration scripts, and everything runs in sequence in the pipeline. A good Elasticsearch deployment resembles a database migration — gradual, tested, and rollback-able.
CI can run a series of tests against a temporary Elasticsearch instance (for example using a docker compose lab cluster, episode 27) — first make sure the cluster is healthy with curl -s localhost:9200/_cluster/health:
steps:
- name: Setup cluster test
run: docker compose up -d
- name: Terapkan template
run: curl -sS -X PUT localhost:9200/_index_template/logs-template --data-binary @index-template-log.yaml
- name: Jalankan integration test
run: python3 -m pytest tests/test_search.pySensible tests:
Measure a baseline with a fixed dataset, then compare: if a query that took 50 ms becomes 500 ms after a mapping change, the pipeline should fail before the change reaches production. This requires a benchmark cluster with representative data — not just "it runs".
Deploying Elasticsearch changes to production follows a safe order:
Terraform declares infrastructure — including Elasticsearch — as code. The community provider elastic/elasticsearch supports index, template, ILM, and user/role resources:
terraform {
required_providers {
elasticsearch = { source = "elastic/elasticsearch", version = "~> 8.0" }
}
}
provider "elasticsearch" {
url = "https://node1:9200"
username = "terraform"
password = var.es_password
insecure = false
}
resource "elasticsearch_index_template" "logs" {
name = "logs-template"
body = jsonencode({
index_patterns = ["logs-*"]
template = { settings = { number_of_shards = 2, number_of_replicas = 1 } }
})
}With terraform plan, you see exactly what will change before terraform apply — and Terraform state detects anything differing from the declaration (drift).
Ansible is great for machine configuration and bootstrap — for example setting vm.max_map_count, installing plugins, and applying elasticsearch.yml:
- name: Siapkan node Elasticsearch
hosts: es_nodes
become: yes
tasks:
- name: Set vm.max_map_count
sysctl: { name: vm.max_map_count, value: "262144" }
- name: Pasang repository Elastic
yum_repository: { name: elastic, state: present }
- name: Install elasticsearch
dnf: { name: elasticsearch, state: present }
- name: Terapkan elasticsearch.yml
template: { src: elasticsearch.yml.j2, dest: /etc/elasticsearch/elasticsearch.yml }
notify: restart elasticsearchThe elasticsearch.yml.j2 templating pattern lets one playbook serve many environments with different values.
GitOps treats git as the single source of truth: changes happen via pull requests, and a tool (Argo CD, Flux, or a custom pipeline) syncs the git state to the live environment. The benefits: every change is recorded in git, there's review, there's an audit trail, and drift is immediately detected because synchronization is automatic.
For Elasticsearch, GitOps means: the repository contains templates, ILM, pipelines, roles — and the pipeline applies them to the cluster when there's a commit on the main branch. Manual cluster changes that aren't in git will be reverted by the next sync.
Configuration drift happens when the cluster diverges from what's declared — for example someone changes settings via a manual console. How to detect it:
GET /_index_template/logs-template?filter_path=*.index_patterns,*.templateA common pattern: a routine pipeline fetches the cluster state (templates, ILM, roles) and compares it with the files in git — if different, the pipeline fails or restores it. Terraform (terraform plan) and GitOps sync both provide this mechanism.
Warning
Drift isn't always human error — it can also be a legitimate side effect of an upgrade or reindex. What matters: drift you don't know about. Apply routine detection mechanisms and document changes that are intentional, so "unknown" differences immediately draw attention.
Configuration only in the console. Undocumented and unreviewable — treat it as code.
Deploying breaking changes directly. New mappings must go through reindex + alias, not a direct overwrite.
No performance regression tests. Slow queries reach production unnoticed.
No drift detection. The cluster drifts from git without anyone knowing.
Secrets in the repository. Passwords and API keys in git mean leakage — use secret managers and pipeline variables.
In episode 28 you mastered CI/CD and infrastructure as code: index templates, ILM policies, and ingest pipelines as versioned files; automated testing (schema validation, query tests, performance regression) in the pipeline; gradual deployment strategies; and Terraform, Ansible, GitOps, and configuration drift detection.
Key takeaways:
Documented and tested configuration makes the system more resilient. But true resilience needs one more layer. In episode 29 we'll cover high availability and disaster recovery: multi-node design with master quorum anti-split-brain, data node redundancy, zone-aware replica allocation; multi-region strategies, RPO and RTO planning, failover procedures, and periodic DR testing. See you there!