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 KarunaratneIshan Karunaratne⏱️ 5 min readUpdated
Share thisCopied

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_name

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
# Indices 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.
  • 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

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts