TechEarl

How to Run Elasticsearch in Docker (With Persistent Storage)

Elasticsearch in a single-node Docker container for development, with a volume on /usr/share/elasticsearch/data, the vm.max_map_count fix, the JVM heap setting, and what to do about security defaults.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Run a single-node Elasticsearch container for development with persistent data, the vm.max_map_count fix on Linux, JVM heap tuning, and the 8.x security defaults that block first-time connections.

A single-node Elasticsearch in Docker is the standard dev setup: one container, dev-mode discovery, modest JVM heap, persistent volume. The image is from docker.elastic.co, not Docker Hub, and there are three things that catch first-time users — a Linux kernel setting, the JVM heap default, and the security defaults that turned on in 8.0.

How do I run Elasticsearch in Docker?

bash
docker run -d --name es \
  -e discovery.type=single-node \
  -e xpack.security.enabled=false \
  -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
  -p 9200:9200 \
  -v es-data:/usr/share/elasticsearch/data \
  docker.elastic.co/elasticsearch/elasticsearch:8.16.0

Single-node mode, security disabled (dev only), 512 MB JVM heap, port 9200 published, data volume mounted. Test it:

bash
curl http://127.0.0.1:9200

The cluster info JSON comes back.

Try it with your own values

Configure version, ports, heap, and volume.

On Linux: fix vm.max_map_count first

Elasticsearch uses memory-mapped files heavily and needs a higher vm.max_map_count than the Linux default. Without this, the container starts but logs an error and refuses to serve requests.

bash
sudo sysctl -w vm.max_map_count=262144

Make it persistent across reboots:

bash
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-elasticsearch.conf

On Docker Desktop (Mac and Windows), this is handled inside Docker's VM — you don't need to set it on the host.

The basic run command

bash
docker run -d --name :container_name 
-e discovery.type=single-node 
-e xpack.security.enabled=false 
-e ES_JAVA_OPTS="-Xms:heap -Xmx:heap" 
-p :host_port:9200 
-v :volume:/usr/share/elasticsearch/data 
docker.elastic.co/elasticsearch/elasticsearch::es_version

Three env vars to know:

  • discovery.type=single-node — tells ES not to look for cluster peers. Required for one-container setups.
  • xpack.security.enabled=false — disables the auth/TLS features turned on by default in 8.0+. For dev only. With it enabled, the first start prints a generated elastic user password and you need it to connect; convenient for production-shaped local setups, friction for quick experiments.
  • ES_JAVA_OPTS="-Xms512m -Xmx512m" — sets the JVM heap (initial = max = 512 MB). The image's default heap can easily eat 1-2 GB on a host that didn't ask for it; pin the size.

Why mount /usr/share/elasticsearch/data

ES stores all indices, shard data, and the cluster state under /usr/share/elasticsearch/data inside the container. Same pattern as the other databases — without a volume, that data is in the container's writable layer and dies with docker rm.

bash
docker stop :container_name && docker rm :container_namedocker run -d --name :container_name 
-e discovery.type=single-node 
-e xpack.security.enabled=false 
-e ES_JAVA_OPTS="-Xms:heap -Xmx:heap" 
-p :host_port:9200 
-v :volume:/usr/share/elasticsearch/data 
docker.elastic.co/elasticsearch/elasticsearch::es_versionIndices are still there.

Practical usage: connecting and indexing

Health check:

bash
curl http://127.0.0.1::host_port/_cluster/health?pretty

Create an index, post a document, search:

bash
curl -X POST http://127.0.0.1::host_port/articles/_doc -H 'Content-Type: application/json' -d '{
"title": "Hello Elasticsearch",
"body": "First document"
}'

curl 'http://127.0.0.1::host_port/articles/_search?pretty&q=Hello'

Pair with Kibana for a UI:

yaml
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch::es_version
environment:
discovery.type: single-node
xpack.security.enabled: "false"
ES_JAVA_OPTS: "-Xms:heap -Xmx:heap"
volumes:
:volume:/usr/share/elasticsearch/data
ports:
"::host_port:9200"
kibana:
image: docker.elastic.co/kibana/kibana::es_version
depends_on:
es
ports:
"5601:5601"
volumes:
:volume:

Kibana on http://127.0.0.1:5601 after both containers start.

Common pitfalls

  • vm.max_map_count too low — set it (see Linux section above). Docker Desktop handles this; bare-metal Linux does not.
  • OOM kills. Set ES_JAVA_OPTS="-Xms512m -Xmx512m" (or larger for real workloads). Without the cap, the JVM can grab gigabytes.
  • Security blocking connections in 8.x. Either set xpack.security.enabled=false for dev, or accept the generated password from the first-start logs.
  • Publishing 9200 to the whole internet. -p 9200:9200 binds to 0.0.0.0, every interface, and Docker can punch that past your host firewall. On a box with a public IP, an unauthenticated node is then reachable from the internet and gets found and wiped by automated bots within hours. Bind to 127.0.0.1:9200:9200 for anything that is not purely local. I learned this the expensive way: how an open Elasticsearch port got my database ransomed.
  • Pulling from Docker Hub instead of docker.elastic.co. The Hub still has an old elasticsearch mirror; the current ES is only on docker.elastic.co. Same with Kibana.
  • Apple Silicon and older ES. The arm64 image arrived in 7.17+; pre-7.17 ES on Apple Silicon needs --platform linux/amd64 and emulation.

What to do next

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsDockerElasticsearchSearchPersistenceDevOps

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts