I personally believe that databases should reside outside your swarm, but for testing we will create a Elasticsearch Container from a Alpine Image to keep the size of our image small, which is also great for a testing environment.

The size of our Elasticsearch 5.6 Image comes down to about 184MB and version 2.4 to about 171MB

Our Dockerfile:

As mentioned, we will build our image from the base Alpine image, as you can see below, and also the rest of our Dockerfile.

Please note: This image is prepared for Docker swarm as I have defined the discovery.zen.ping.unicast.hosts value to a hostname, which will be the same as my master elasticsearch service's name, which will be resolvable.

If you are using this without swarm, you can simple remove the last parameter.

FROM alpine:latest

RUN apk update \
    && apk upgrade \
    && apk add curl wget bash openssl openjdk8 \
    && rm -rf /var/cache/apk/*

WORKDIR /root/

RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.0.tar.gz -O elasticsearch-5.6.0.tar.gz

RUN tar -xf  elasticsearch-5.6*.tar.gz -C /usr/local/ \
    && mv /usr/local/elasticsearch-5.6* /usr/local/elasticsearch \
    && mkdir /usr/local/elasticsearch/logs \
    && mkdir /usr/local/elasticsearch/data \
    && echo '-Xms512m' > /usr/local/elasticsearch/config/jvm.options \
    && echo '-Xmx512m' >> /usr/local/elasticsearch/config/jvm.options \
    && adduser -D -u 1000 -h /usr/local/elasticsearch elasticsearch \
    && chown -R elasticsearch /usr/local/elasticsearch

USER elasticsearch

CMD ["/usr/local/elasticsearch/bin/elasticsearch", "-Ecluster.name=es-cluster", "-Enode.name=${HOSTNAME}", "-Epath.data=/usr/local/elasticsearch/data", "-Epath.logs=/usr/local/elasticsearch/logs", "-Enetwork.host=0.0.0.0", "-Ediscovery.zen.ping.unicast.hosts=es-master"]

EXPOSE 9200 9300

Building our Image:

While we have our Dockerfile in our current working directory, we can run the following:

$ docker build -t 'es:5.6' .

Pushing your Image:

You can then push your image to any registry of choice, for this example let's push it to Docker Hub:

$ docker login
$ docker tag es:5.6 <username>/es:5.6
$ docker push <username>es:5.6

Creating your Elasticsearch Cluster:

Create the Overlay Network:

$ docker network create --driver=overlay appnet

Let's create the Master (aka Exposed Entrypoint), this need to match the same name as mentioned before:

$ docker service create --name es-master -p 9200:9200 --network appnet --replicas 1  --with-registry-auth <username>/es:5.6

Wait until the service reached the desired replica count, which in this case is 1. Then we can continue to create 3 other data nodes:

$ docker service create --name es-data-1  --network appnet --replicas 1 --with-registry-auth <username>/es:5.6

$ docker service create --name es-data-2  --network appnet --replicas 1 --with-registry-auth <username>/es:5.6

$ docker service create --name es-data-3  --network appnet --replicas 1 --with-registry-auth <username>/es:5.6

After some time, we should see an output more or less like the following:

$ docker service ls -f name=es
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
mf80ezthqw5b        es-data             replicated          1/1                 es:5.6
j54v3rzs15km        es-data-2           replicated          1/1                 es:5.6
n02029dtosr9        es-data-3           replicated          1/1                 es:5.6
r2mfhekkvv6b        es-master           replicated          1/1                 es:5.6              *:9200->9200/tcp

Query Our Elasticsearch Cluster:

Let's have a look at our Cluster Health API:

$ curl -XGET http://127.0.0.1:9200/_cluster/health?pretty
{
  "cluster_name" : "es-cluster",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 4,
  "number_of_data_nodes" : 4,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "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
}

And also our Nodes API:

$ curl -XGET http://127.0.0.1:9200/_cat/nodes?v
ip        heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.0.2.11           31          76   7    0.33    0.34     0.55 mdi       -      1caebab2d4a4
10.0.2.19           31          76   7    0.33    0.34     0.55 mdi       -      8b494325c714
10.0.2.10           35          76   7    0.33    0.34     0.55 mdi       -      0f64c27257eb
10.0.2.6            33          76   7    0.33    0.34     0.55 mdi       *      01e903680e00

Further Actions:

I have a WIP cheatsheet of some Elasticsearch examples, which you can follow on one of my Elasticsearch Cheatsheet Gists

Thats it for now :D