Skip to main content
Time to turn the plan into real Kubernetes objects. In this walkthrough we create a dedicated namespace, persistent storage, a Service, and a StatefulSet that runs Elasticsearch with persistent data and correct permissions. Follow the steps in order — the manifests and objects depend on each other. Why create a separate namespace? It keeps the Elasticsearch stack objects isolated from your application objects and makes cleanup and management easier.
A slide titled "A Home for the Stack" showing a user avatar labeled "Bob" on the left and three circular icons for Fluentd, Elasticsearch, and Kibana across the top, with a dashed outline of a folder beneath them.

1 — Create a namespace and set it as default

Create a named “folder” for the Elasticsearch objects and make it the default for your kubectl session so subsequent commands don’t need -n elastic-stack:
Using a dedicated namespace (elastic-stack) isolates the EFK/Elasticsearch objects (PersistentVolumes, Services, StatefulSets, etc.) from your application namespaces and simplifies resource management and RBAC.

2 — PersistentVolume (the disk)

Decide the storage first so the StatefulSet can bind to it. Create a PersistentVolume manifest for pv-elasticsearch that exposes a 5Gi hostPath on the node. Example:
Apply it:
The volume now exists in the cluster in the “Available” (unbound) state, awaiting a PersistentVolumeClaim.

3 — Service (the address) — create before pods

A Service provides a stable network address for Elasticsearch. Create a NodePort Service named elasticsearch that exposes:
  • port 9200 — HTTP, search/data API
  • port 9300 — inter-node transport
Example snippet from the Service manifest:
Apply it:
Service ports mapping: Why create the Service first? Services route by labels, so when matching pods appear they will be immediately discoverable.

4 — StatefulSet (the Elasticsearch pods)

A StatefulSet gives predictable identities and stable storage. Key configuration pieces:
  • metadata.name: elasticsearch and pod label app=elasticsearch
  • container image: docker.elastic.co/elasticsearch/elasticsearch:7.1.0
  • discovery.type=single-node so the cluster knows it’s intentionally running alone (for single-node setups)
StatefulSet metadata and selector example:
Container spec (image, ports, env, volume mounts):
Note: some Kubernetes API servers restrict environment variable names to C identifiers (no dots). If your cluster rejects an env var named discovery.type, pass the setting on the Elasticsearch command line (for example, start Elasticsearch with -E discovery.type=single-node) or provide the setting in the configuration file.

Persistent storage for the StatefulSet

Use volumeClaimTemplates so each pod gets a PersistentVolumeClaim that binds to the pv-elasticsearch we created earlier:

5 — Fix filesystem ownership (init container)

Elasticsearch runs as UID 1000. A brand-new hostPath directory on the node is typically owned by root and not writable by UID 1000. This prevents Elasticsearch from starting. Fix ownership before the main container starts using an init container that runs chown on the mount.
The data directory on the host must be writable by Elasticsearch (UID 1000). If permissions are incorrect, the main container will fail to start. The initContainer below corrects ownership before Elasticsearch starts.
Init container example:
Apply the StatefulSet manifest (kubectl apply -f elasticsearch-statefulset.yaml) and watch Kubernetes create the first pod elasticsearch-0. StatefulSet pods are numbered predictably (e.g., elasticsearch-0, elasticsearch-1). Be patient: Elasticsearch can take some time to initialize. During startup the PVC will bind to pv-elasticsearch, the init container will run and exit successfully, and then the main container will boot.
A diagram titled "Be Patient While It Starts" showing a sleepy Elasticsearch pod labeled "elasticsearch-0" on a node, with an arrow pointing to a cylindrical persistent volume labeled "pv-elasticsearch." A small mascot rests on the node and the word "Bound" appears below.

6 — Verify readiness

Check Elasticsearch from multiple angles:
  • Pods: kubectl get pods -l app=elasticsearch — wait for the READY column to indicate readiness.
  • Endpoints: kubectl get endpoints elasticsearch — the Service should show the IP of elasticsearch-0.
  • HTTP API: from a node, curl the NodePort:
Example JSON response:
You can also request the cluster state with pretty formatting for more detail.

Summary of created objects

At this point Elasticsearch is running, reachable via the Service NodePort, and its data directory is persisted on the host. The index data will remain across pod restarts and reschedules. The cluster is healthy but still empty until your application starts writing documents.

Watch Video

Practice Lab