Running Elasticsearch in Docker: Setup, Compose, and Production Tips

Elasticsearch ships official Docker images at docker.elastic.co/elasticsearch/elasticsearch. The image is the fastest way to spin up a development cluster, run integration tests, or evaluate a new version. Production Docker deployments are viable but require attention to memory locking, file descriptor limits, persistent storage, and Elasticsearch's 8.x security defaults - which differ noticeably from 7.x.

Prerequisites

  • Docker Engine 20.10 or later

  • At least 2 GB of RAM available to the container (4 GB+ recommended)

  • Linux host with vm.max_map_count set to at least 262144:

    sudo sysctl -w vm.max_map_count=262144
    

    Add vm.max_map_count=262144 to /etc/sysctl.conf to persist across reboots.

Step 1: Pull the Official Image

Pin a specific version - never use latest in production:

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.15.3

The 9.x line is current as of mid-2026; pick the version matching your stack components (Kibana, Logstash, Beats, clients).

Step 2: Run a Single-Node Container

A development-friendly command with security disabled:

docker run -d --name elasticsearch \
  -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
  docker.elastic.co/elasticsearch/elasticsearch:8.15.3

Key flags:

Flag Purpose
-p 9200:9200 REST API
-p 9300:9300 Transport (only needed for multi-node)
discovery.type=single-node Skip cluster bootstrapping
xpack.security.enabled=false Local development only - never in production
ES_JAVA_OPTS Heap size, set -Xms == -Xmx

For production-style setup with security enabled (the 8.x default), omit xpack.security.enabled=false and let Elasticsearch auto-generate the elastic user password on first start. Capture it from the container logs:

docker logs elasticsearch | grep -i "Password for the elastic user"

Step 3: Verify

curl http://localhost:9200/

With security enabled:

curl -u elastic:<password> http://localhost:9200/

The response is a JSON document with the cluster name, version, and Lucene version.

Step 4: Docker Compose Setup

A reproducible single-node config with persistent storage and memory locking:

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - ES_JAVA_OPTS=-Xms2g -Xmx2g
      - xpack.security.enabled=true
      - ELASTIC_PASSWORD=changeme
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    ports:
      - "9200:9200"
    volumes:
      - esdata:/usr/share/elasticsearch/data

volumes:
  esdata:
    driver: local

Start it:

docker compose up -d

Step 5: Multi-Node Cluster

A three-node cluster with discovery.seed_hosts and cluster.initial_master_nodes:

services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
    environment:
      - node.name=es01
      - cluster.name=demo-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - ES_JAVA_OPTS=-Xms2g -Xmx2g
    ulimits:
      memlock: { soft: -1, hard: -1 }
    volumes:
      - esdata01:/usr/share/elasticsearch/data
    ports: ["9200:9200"]

  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
    environment:
      - node.name=es02
      - cluster.name=demo-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - ES_JAVA_OPTS=-Xms2g -Xmx2g
    ulimits:
      memlock: { soft: -1, hard: -1 }
    volumes:
      - esdata02:/usr/share/elasticsearch/data

  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
    environment:
      - node.name=es03
      - cluster.name=demo-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - ES_JAVA_OPTS=-Xms2g -Xmx2g
    ulimits:
      memlock: { soft: -1, hard: -1 }
    volumes:
      - esdata03:/usr/share/elasticsearch/data

volumes: { esdata01: , esdata02: , esdata03: }

For real production, enable TLS for the transport layer and HTTP - the 8.x image generates self-signed certs automatically on first start if security is enabled, or you can mount certs into /usr/share/elasticsearch/config/certs.

Production Considerations

Concern Recommendation
Storage Use named volumes or bind mounts; never rely on container layer storage
Memory locking bootstrap.memory_lock=true plus ulimits.memlock: -1
Heap size -Xms == -Xmx, under 50% of container memory, no more than ~30 GB
File descriptors Set ulimits.nofile to 65536+
Security Enabled by default in 8.x - keep it that way
Networking Use a dedicated Docker network; don't expose port 9300 to the internet
Image pinning Pin to a specific version, never latest
Health checks Configure HEALTHCHECK on the container

Common Pitfalls

  1. Running without vm.max_map_count raised. The container starts but fails to mmap segments, hitting bizarre errors at runtime.
  2. Setting discovery.type=single-node on what's supposed to be a multi-node cluster. The node ignores discovery.seed_hosts.
  3. Mounting a non-empty data directory from a different cluster's volume. The node starts but joins the wrong cluster or fails on UUID mismatch.
  4. Letting the container OOM-killer kill Elasticsearch. The default container memory limit isn't aware of the JVM heap - set a Docker memory limit that exceeds heap by 1-2 GB.
  5. Forgetting to copy the auto-generated 8.x security credentials before recreating the container. The credentials are stored in the data volume but printed only on first start.

Operating Elasticsearch in Docker

Pulse connects to Elasticsearch running in Docker, Docker Compose, or Kubernetes the same way it connects to bare-metal clusters - via the REST API. Configuration drift between containers, missing memlock, undersized heaps, and discovery misconfigurations are surfaced automatically, so the operational cost of running Elasticsearch in containers stays manageable as the stack grows.

Frequently Asked Questions

Q: How do I run Elasticsearch in Docker?
A: docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:<version>. The 8.x image enables security by default and prints the elastic user password on first start.

Q: What's the minimum memory for Elasticsearch in Docker?
A: 2 GB total container memory is the minimum to start; 4 GB is more practical. Set ES_JAVA_OPTS to -Xms1g -Xmx1g (or higher) - heap should be at most 50% of container memory.

Q: How do I get the elastic user password in Docker 8.x?
A: On first start, Elasticsearch logs the auto-generated password: docker logs elasticsearch | grep "Password for the elastic user". To reset, run docker exec -it elasticsearch bin/elasticsearch-reset-password -u elastic.

Q: Can I run a multi-node Elasticsearch cluster in Docker?
A: Yes - use Docker Compose with each node defined as a service, set cluster.initial_master_nodes and discovery.seed_hosts, and give each node its own data volume. Avoid discovery.type=single-node, which disables cluster bootstrapping.

Q: Why does my Elasticsearch container fail with bootstrap check errors?
A: Most often vm.max_map_count is below 262144 on the host. Run sudo sysctl -w vm.max_map_count=262144. Other causes: memlock ulimit not raised, file descriptors below 65535, heap settings inconsistent.

Q: Is it safe to run Elasticsearch in Docker in production?
A: Yes, but pay attention to persistent storage (named volumes), ulimits (memlock, nofile), heap sizing, security (keep it enabled), and version pinning. The container runtime adds no real overhead; misconfiguration is the usual source of trouble.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.