ELK Stack and OpenSearch skill for Codex
Data streams, sane shards and fields you can query. Codex snapshots cluster health read-only, then designs log storage around data streams with explicit ECS-aligned mappings, dynamic mapping turned off for unknown keys, and lifecycle policies that roll over at 10 to 50 GB shards. It tests ingest pipelines with _simulate, writes KQL and ES|QL for investigations, and asks before any delete or retention cut.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- elk-stack Installs to ~/.agents/skills/elk-stack/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install elk-stack The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project elk-stack Installs to .agents/skills/elk-stack/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/elk-stack.zipunzip -o elk-stack.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) elk-stack PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $elk-stack, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds and tunes log pipelines on Elasticsearch, Logstash and Kibana or OpenSearch - ECS field mapping, index templates and data streams, ILM or ISM rollover and retention, shard sizing, ingest pipelines and Logstash filters, mapping explosion fixes, and KQL and ES|QL queries for investigations. Use when shipping logs into Elasticsearch or OpenSearch, when a cluster turns yellow or red or runs out of disk, when mappings conflict or fields explode, when retention costs too much, or when writing Kibana queries and dashboards.
Use it when
- Logs need to be shipped into Elasticsearch or OpenSearch with proper mappings
- The cluster is yellow, red or blocked on disk
- Field limits or mapping conflicts reject documents
- Retention and shard counts are out of control
Use something else when
- Logs are stored in Grafana Loki: use loki-logs
- You need full-text product search on Elasticsearch rather than logs: use elasticsearch-mappings
How to check the result
A sample through the real pipeline is searchable with correctly typed fields, the data stream shows the intended lifecycle policy, and shard sizes and disk headroom are in range after a week.
The mistake it prevents
Letting dynamic mapping index every JSON key from every service, which hits the field limit and creates type conflicts across apps.
Done well means: logs land in data streams with an explicit, ECS-aligned mapping; indices roll over at a sensible shard size and age out automatically; the cluster stays green with headroom on disk; and investigators can query by fields rather than grepping message text.
Inspect first
- Read
AGENTS.md; find shipper and pipeline configs (Elastic Agent or Filebeat, Logstash*.conf, Fluent Bit, OTel Collectorelasticsearchexporter) and any index templates or ILM/ISM policies in the repo. - Confirm product and version: Elasticsearch 8.x/9.x or OpenSearch 2.x/3.x. APIs differ
(ILM vs ISM, ES|QL vs PPL).
curl -s $ES/shows it. - Cluster health snapshot (read-only, needs network and credentials from the user’s environment, never hard-coded):
curl -s "$ES/_cluster/health?pretty"
curl -s "$ES/_cat/nodes?v&h=name,node.role,heap.percent,disk.used_percent,cpu"
curl -s "$ES/_cat/indices?v&s=store.size:desc&h=index,health,pri,rep,docs.count,store.size" | head -20
curl -s "$ES/_cat/shards?v&s=store:desc" | head -20
curl -s "$ES/_cat/allocation?v"
Index layout
Use data streams for logs (append-only, time-based), named
logs-<dataset>-<namespace>, for example logs-checkout.app-prod. The Elastic Agent
and recent Beats create these with built-in templates; for custom apps, define your own.
PUT _index_template/logs-checkout
{
"index_patterns": ["logs-checkout.*-*"],
"data_stream": {},
"priority": 200,
"composed_of": ["logs-mappings", "logs-settings"],
"template": {
"settings": {
"index.lifecycle.name": "logs-30d",
"index.mapping.total_fields.limit": 2000
},
"mappings": {
"dynamic": "false",
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "match_only_text" },
"log.level": { "type": "keyword" },
"service.name": { "type": "keyword" },
"trace.id": { "type": "keyword" },
"http.response.status_code": { "type": "long" },
"event.duration": { "type": "long" },
"labels": { "type": "flattened" }
}
}
}
}
Mapping rules:
- Follow ECS names (
service.name,log.level,trace.id,http.response.status_code) so Kibana apps and correlations work. keywordfor anything you filter or aggregate on;match_only_textortextonly for free text you search by words.dynamic: false(unknown fields stored in_source, not indexed) orruntimestops mapping explosions from arbitrary JSON keys. Put unpredictable key-value bags in aflattenedfield.- One field, one type across all services sharing the index pattern.
statusas string in one app and number in another causes rejected documents or conflicts.
Lifecycle and shard sizing
PUT _ilm/policy/logs-30d
{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_primary_shard_size": "50gb", "max_age": "1d" } } },
"warm": { "min_age": "3d", "actions": { "shrink": { "number_of_shards": 1 }, "forcemerge": { "max_num_segments": 1 } } },
"delete": { "min_age": "30d", "actions": { "delete": {} } }
}
}
}
- Target primary shards of 10-50 GB; tiny daily shards for small services waste heap and slow the cluster. Before Elasticsearch 8.3 the rule of thumb was at most 20 shards per GB of heap; newer versions are more efficient but shard count still costs.
- Rollover by size and age, never by date-named indices written directly.
- OpenSearch uses Index State Management (
_plugins/_ism/policies/<name>) with states and transitions instead of ILM phases; the concepts map one to one. - Long retention at low cost: snapshots to object storage, searchable snapshots or a frozen tier (license-dependent), or archive raw logs to S3 and keep 7-30 days hot.
Changing retention or deleting indices removes data permanently; show the exact indices
or policy diff and get explicit confirmation before any DELETE or policy change that
shortens retention.
Parsing
Prefer structured JSON logs from the app (see $structured-logging); parsing text in
the pipeline is fragile and CPU-hungry. When you must parse:
Ingest pipeline (runs in Elasticsearch, no extra service):
PUT _ingest/pipeline/nginx-access
{
"processors": [
{ "grok": { "field": "message", "patterns": ["%{IPORHOST:source.ip} - %{DATA:user.name} \\[%{HTTPDATE:ts}\\] \"%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}\" %{NUMBER:http.response.status_code:int} %{NUMBER:http.response.body.bytes:long}"] } },
{ "date": { "field": "ts", "formats": ["dd/MMM/yyyy:HH:mm:ss Z"] } },
{ "remove": { "field": "ts" } }
],
"on_failure": [ { "set": { "field": "error.message", "value": "{{ _ingest.on_failure_message }}" } } ]
}
Test before attaching: POST _ingest/pipeline/nginx-access/_simulate with sample docs.
Logstash is worth running only for heavy enrichment, buffering or fan-out; its
elasticsearch output should use data_stream => true.
Querying
KQL in Kibana Discover:
service.name : "checkout" and log.level : "error" and not url.original : *healthz*
http.response.status_code >= 500 and trace.id : *
ES|QL (Elasticsearch 8.11+) for aggregations without building a visualization:
FROM logs-checkout.*
| WHERE @timestamp > NOW() - 1 hour AND http.response.status_code >= 500
| STATS errors = COUNT(*) BY url.original
| SORT errors DESC
| LIMIT 20
OpenSearch equivalents: DQL in Dashboards and PPL (source=logs-* | where ... | stats count() by ...).
Troubleshooting
| Symptom | Check | Typical fix |
|---|---|---|
| Cluster yellow | _cluster/allocation/explain | Replicas cannot allocate (single node: set replicas 0 for dev only) |
| Cluster red | Same, for primaries | Restore from snapshot or recover node; do not delete blindly |
Writes blocked, flood_stage | Disk > 95% | Free space or add nodes, then the read-only block clears automatically in recent versions |
Limit of total fields exceeded | Mapping size | dynamic: false or flattened; fix the source app |
mapper_parsing_exception | Type conflict | Align field types; reindex or wait for rollover |
| Slow queries | Leading wildcards, huge time ranges, text aggregations | Use keyword fields, narrow range |
Verification
- Index a sample through the real pipeline and
GET <data-stream>/_search?size=1shows parsed fields with correct types;GET <data-stream>/_mappinghas no surprise fields. GET _data_stream/<name>shows the expected ILM/ISM policy;GET <index>/_ilm/explainshows the phase progressing.- After a week, shard sizes are in range and cluster disk has at least 20-25% free.
Output
Report the index template, lifecycle policy, pipeline, expected daily volume and shard math (GB/day / target shard size), retention and its cost implication, and the queries or dashboards delivered.
Gotchas
- Templates apply only to new indices; a fix requires rollover (
POST <stream>/_rollover). - Kibana data views on
logs-*with conflicting field types show “conflict” and break visualizations. - Running ingest without
@timestampfrom the event uses ingest time, which skews every timeline after a backlog. - Security: never expose Elasticsearch or OpenSearch ports publicly; keep auth and TLS on.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "ELK Stack and OpenSearch"
short_description: "Data streams, sane shards and fields you can query"
default_prompt: "$elk-stack Our Elasticsearch cluster is yellow and almost out of disk, figure out why" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.