Skip to content
ExponaBLOG
ELASTICSEARCH & SEARCH ENGINEERING · DAY 003 / 100

Running Elasticsearch locally: best practices explained

Start with Docker in minutes.

Expona Admin7 min read

Introduction

Getting Elasticsearch running on your machine shouldn't require a production infrastructure setup. Many teams waste hours wrestling with complex configurations before they've even indexed their first document. Running Elasticsearch locally: best practices explained means understanding that a well-configured single-node cluster in Docker gives you 90% of what you need to test indexing strategies, query patterns, and aggregations without the operational overhead.

This guide walks you through spinning up a development environment in minutes, configuring it properly, and avoiding the pitfalls we've seen slow down integration work on real projects. Whether you're prototyping a search feature for an e-commerce platform or building a CRM search backend, these practices will save your team setup time and prevent common frustrations.

Single node — A local cluster for development

A single-node Elasticsearch cluster is the ideal starting point for local development. It removes the complexity of node discovery, replica management, and shard allocation—all things that matter in production but only complicate early-stage work.

Here's the fastest way to get started:

bash
docker run -p 9200:9200 \
  -e discovery.type=single-node \
  -e xpack.security.enabled=false \
  elasticsearch:8.15.0

Breaking down what each flag does:

  • -p 9200:9200 — Exposes the REST API on your localhost. Port 9200 is the Elasticsearch standard.
  • discovery.type=single-node — Tells Elasticsearch it's running alone. Without this, it waits indefinitely for other nodes and never becomes healthy.
  • xpack.security.enabled=false — Disables authentication. Fine for development; never do this in any environment with real data or network access.
  • elasticsearch:8.15.0 — Use a specific version to ensure consistency across your team. Pin this in documentation so everyone develops against the same behavior.

Within seconds, Elasticsearch listens on http://localhost:9200. You can now index documents and run queries. Many teams skip the version pinning step and then spend hours debugging because one person is on 8.14 and another on 8.15—don't make that mistake.

If you're on M-series Macs or ARM64 systems, add a platform flag to avoid emulation slowdown:

bash
docker run --platform linux/amd64 -p 9200:9200 \
  -e discovery.type=single-node \
  -e xpack.security.enabled=false \
  elasticsearch:8.15.0

This runs the Linux x86 image natively through Docker's emulation, which is still faster than Alpine-based alternatives for Elasticsearch.

Kibana — Add Kibana for Dev Tools and dashboards

The Elasticsearch REST API works fine from curl, but Kibana's Dev Tools console is where real development speed happens. The syntax highlighting, request history, and immediate feedback loop will accelerate your indexing and query work significantly.

Add Kibana to your setup with a simple Docker Compose file:

json
version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:8.15.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5

  kibana:
    image: kibana:8.15.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

Start both services with:

bash
docker-compose up -d

Kibana becomes available at http://localhost:5601. Navigate to Dev Tools (usually in the left sidebar under "Management"), and you'll see the Console tab. This is where you'll spend most of your development time: writing and testing mapping definitions, indexing sample documents, and refining queries.

A real-world example: if you're building search for an e-commerce catalog, you'd open the Console and define your index mapping first:

json
PUT /products
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "standard"
      },
      "category": {
        "type": "keyword"
      },
      "price": {
        "type": "float"
      },
      "created_at": {
        "type": "date"
      }
    }
  }
}

Then index test documents and iterate on your queries without leaving the browser. Kibana's history feature saves every request, so you can scroll back and reuse patterns from earlier work. This is invaluable when you're tuning relevance or experimenting with different filter strategies.

Check health — GET _cluster/health

Before indexing anything, always verify that your cluster is ready. The _cluster/health endpoint tells you whether shards are allocated and the cluster can accept writes.

bash
curl http://localhost:9200/_cluster/health

You'll get a response like:

json
{
  "cluster_name": "elasticsearch",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 1,
  "active_shards": 1,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 0,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 100.0
}

The status field tells the story: green means all primary and replica shards are allocated and healthy. yellow means primaries are allocated but replicas aren't (normal in single-node setups before you've tuned replica counts). red means something is wrong—shards can't be allocated, usually due to misconfiguration or storage issues.

In development, you'll usually see green immediately because we set replicas to 0 in the Docker Compose example. If you see yellow or red, check the cluster state:

bash
curl http://localhost:9200/_cluster/state?pretty

This shows unassigned shards and their reasons. Common causes include:

  • Not enough data nodes for replica allocation (expected in single-node; set replicas to 0).
  • Disk space too low (Elasticsearch reserves 5% by default; if your Docker volume is nearly full, it won't allocate).
  • Node rejecting writes due to circuit breaker limits (usually memory; restart the container).

A script in your README that checks health before running tests prevents integration surprises:

bash
#!/bin/bash
for i in {1..30}; do
  STATUS=$(curl -s http://localhost:9200/_cluster/health | jq -r '.status')
  if [ "$STATUS" = "green" ]; then
    echo "Cluster is ready"
    exit 0
  fi
  echo "Waiting for cluster... ($i/30)"
  sleep 1
done
echo "Cluster failed to become healthy"
exit 1

Or OpenSearch — The open-source fork works similarly

OpenSearch is a community-driven fork of Elasticsearch maintained by AWS. If your organization prefers open-source governance or wants to avoid Elastic's licensing, OpenSearch is a drop-in replacement for most development workflows.

The Docker setup is nearly identical:

bash
docker run -p 9200:9200 \
  -e discovery.type=single-node \
  -e DISABLE_SECURITY_PLUGIN=true \
  opensearchproject/opensearch:2.11.0

And with Docker Compose:

json
version: '3.8'
services:
  opensearch:
    image: opensearchproject/opensearch:2.11.0
    environment:
      - discovery.type=single-node
      - DISABLE_SECURITY_PLUGIN=true
      - JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - "9200:9200"

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.11.0
    environment:
      - OPENSEARCH_HOSTS=["http://opensearch:9200"]
    ports:
      - "5601:5601"
    depends_on:
      - opensearch

The REST API is identical—all the examples in this guide work unchanged. Dev Tools work the same way. The main differences are:

  • Environment variables use different naming (e.g., DISABLE_SECURITY_PLUGIN instead of xpack.security.enabled).
  • OpenSearch Dashboards replaces Kibana (same UI, different branding).
  • Some newer Elasticsearch features may lag behind in OpenSearch, but core search functionality is stable.

If your team is already committed to OpenSearch, use it for local development too. Version consistency matters more than which fork you choose. A CRM or trading backend using OpenSearch in production should test locally against OpenSearch, not Elasticsearch.

Common mistakes

  • Forgetting discovery.type=single-node — Your cluster will hang on startup waiting for other nodes. It never becomes healthy, and you'll spend 20 minutes confused before realizing the flag is missing.
  • Enabling security without credentials configured — If xpack.security.enabled=true but you haven't set a password, you'll get 401 errors on every request and waste time troubleshooting authentication instead of search logic.
  • Not pinning versions — A team member updates Docker images and suddenly queries behave differently. Always specify elasticsearch:8.15.0, not elasticsearch:latest.
  • Setting replicas=1 in single-node clusters — This leaves shards unassigned because there's no second node. You'll see yellow status indefinitely. Single-node clusters should have "number_of_replicas": 0.
  • Not checking health before running tests — Your test suite tries to index into a cluster that's still starting up, and you get flaky failures. Add health checks to your test setup.
  • Using too much memory — Elasticsearch defaults to 1GB heap. On a laptop with other services running, add -e "ES_JAVA_OPTS=-Xms256m -Xmx256m" to avoid thrashing.

Key takeaways

  • Start with a single-node cluster in Docker; it removes operational complexity and is fast to iterate with.
  • Always use discovery.type=single-node and disable security for local development; enable security only when you need to test authentication logic.
  • Pin your Elasticsearch or OpenSearch version in Docker Compose to keep your team in sync.
  • Add Kibana or OpenSearch Dashboards immediately; the Dev Tools console is where search development really happens.
  • Check cluster health with GET _cluster/health before indexing; it's your first signal that something is wrong.
  • Use Docker Compose instead of raw docker run commands; it's repeatable and team-friendly.
  • Set replicas to 0 in single-node clusters; replicas belong in staging and production, not development.
  • OpenSearch is a viable alternative if your organization requires open-source; the REST API and local development experience are nearly identical.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles