> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hands On Elasticsearch Deployment

> Step-by-step guide to deploy single-node Elasticsearch on Kubernetes using a namespace, PersistentVolume, Service, StatefulSet, and init container for persistent storage and correct permissions

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.](https://mintcdn.com/kodekloud-c4ac6d9a/YjasonRdhEsLdkKY/images/Learn-By-Doing-Deploying-and-Managing-the-EFK-Stack-on-Kubernetes/Deploying-Elasticsearch/Hands-On-Elasticsearch-Deployment/home-stack-bob-fluentd-elasticsearch-kibana.jpg?fit=max\&auto=format\&n=YjasonRdhEsLdkKY\&q=85\&s=e65590cde93941d8eeb5538d8497e546)

## 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`:

```bash theme={null}
kubectl create namespace elastic-stack
kubectl config set-context --current --namespace=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:

```yaml theme={null}
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-elasticsearch
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /data/elasticsearch
```

Apply it:

```bash theme={null}
kubectl apply -f es-pvolume.yaml
# persistentvolume/pv-elasticsearch created
```

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:

```yaml theme={null}
ports:
  - port: 9200
    targetPort: 9200
    nodePort: 30200
    name: port1
  - port: 9300
    targetPort: 9300
    nodePort: 30300
    name: port2
type: NodePort
```

Apply it:

```bash theme={null}
kubectl apply -f es-service.yaml
# service/elasticsearch created
```

Service ports mapping:

| Service port | Purpose | NodePort |
| - | - | - |
| 9200 | HTTP / REST / search & data API | `30200` |
| 9300 | Elasticsearch inter-node transport | `30300` |

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:

```yaml theme={null}
metadata:
  name: elasticsearch
  namespace: elastic-stack
spec:
  serviceName: "elasticsearch"
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
```

Container spec (image, ports, env, volume mounts):

```yaml theme={null}
spec:
  containers:
    - name: elasticsearch
      image: docker.elastic.co/elasticsearch/elasticsearch:7.1.0
      ports:
        - containerPort: 9200
          name: port1
        - containerPort: 9300
          name: port2
      env:
        - name: discovery.type
          value: "single-node"
      volumeMounts:
        - name: es-data
          mountPath: /usr/share/elasticsearch/data
```

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:

```yaml theme={null}
volumeClaimTemplates:
  - metadata:
      name: es-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
```

## 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:

```yaml theme={null}
initContainers:
  - name: fix-permissions
    image: busybox
    command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
    securityContext:
      privileged: true
    volumeMounts:
      - name: es-data
        mountPath: /usr/share/elasticsearch/data
```

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.](https://mintcdn.com/kodekloud-c4ac6d9a/YjasonRdhEsLdkKY/images/Learn-By-Doing-Deploying-and-Managing-the-EFK-Stack-on-Kubernetes/Deploying-Elasticsearch/Hands-On-Elasticsearch-Deployment/sleepy-elasticsearch-pod-pv-bound.jpg?fit=max\&auto=format\&n=YjasonRdhEsLdkKY\&q=85\&s=eb4c51c8e1f7cd50ca0403e0eac36066)

## 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:

```bash theme={null}
curl http://localhost:30200
```

Example JSON response:

```json theme={null}
{
  "name": "elasticsearch-0",
  "cluster_name": "docker-cluster",
  "version": {
    "number": "7.1.0"
  },
  "tagline": "You Know, for Search"
}
```

You can also request the cluster state with pretty formatting for more detail.

## Summary of created objects

| Resource Type | Name / Purpose |
| - | - |
| Namespace | `elastic-stack` (isolates the stack) |
| PersistentVolume | `pv-elasticsearch` (5Gi hostPath) |
| PersistentVolumeClaim | `es-data-<pod>` (created by StatefulSet) |
| Service (NodePort) | `elasticsearch` (ports 9200 -> 30200, 9300 -> 30300) |
| StatefulSet | `elasticsearch` (single-node Elasticsearch) |

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.

## Links and references

* [Kubernetes: Services](https://kubernetes.io/docs/concepts/services-networking/service/)
* [Kubernetes: StatefulSets](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)
* [Kubernetes: PersistentVolumes and PersistentVolumeClaims](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
* [Elasticsearch: Configuration](https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html)

> [card] [Watch Video](https://learn.kodekloud.com/user/courses/learn-by-doing-deploying-and-managing-the-efk-stack-on-kubernetes/module/888e8799-326d-4188-a1df-3096a501a818/lesson/595aa040-dcde-4153-a14c-9957bb82e563)

> [card] [Practice Lab](https://learn.kodekloud.com/user/courses/learn-by-doing-deploying-and-managing-the-efk-stack-on-kubernetes/module/888e8799-326d-4188-a1df-3096a501a818/lesson/5b0f2786-bd91-4957-878b-87c328cb5b9c)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.