Skip to content

programmerShinobi/elasticsearch-docker-dev-setup

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

elasticsearch-docker-dev-setup

Single-node Elasticsearch on Docker, tuned for local development.
Stable, lightweight, and instantly usable by a host-local NestJS backend at http://localhost:9200 — without slowing your machine down.

Elasticsearch Docker Mode License


Tip

New to Elasticsearch, Docker, or NestJS? Start with the zero-assumptions Beginner's Tutorial — it explains every term and walks you through your first run step by step.

📖 Table of Contents


🧭 Overview

This repository provides a peace-of-mind, development-grade Elasticsearch environment. It runs one Elasticsearch node in Docker and is intentionally not a cluster. The goal is a database that is:

  • Reachable from a local (non-Docker) NestJS app via http://localhost:9200
  • Memory-bounded so your IDE, browser, and backend stay responsive
  • Resilient — never enters RED, auto-restarts, persists data across reboots

It is designed to live next to an already-running Dockerized Redis without interfering with it.


🤔 Why This Setup

Running Elasticsearch on a developer laptop usually goes wrong in two ways:

  1. It eats all the RAM → the whole system lags.
  2. It crash-loops or goes RED → the backend can't connect.

This setup solves both by hard-capping memory (1 GB heap inside a 1.5 GB container), locking memory to prevent swap, disabling unused features (security, ML, Kibana, clustering), and gating health on green/yellow.

Goal How it's achieved
NestJS connects via localhost Port 9200:9200 published to the host
System stays fast mem_limit: 1536m + ES_JAVA_OPTS=-Xms1g -Xmx1g
CPU stays under control cpus: 4.0 (half of 8 threads) + node.processors=4
No swap thrash bootstrap.memory_lock=true + memlock ulimit
Never RED Single-node primaries; healthcheck on green/yellow; stop_grace_period: 60s for clean shutdown
No restart loops restart: unless-stopped + vm.max_map_count preset
Not exposed on the network 127.0.0.1:9200 loopback bind (security is off)
No disk creep over time logging capped at 10m × 3 + nofile raised
Data survives restarts Named esdata volume

Note

A single-node cluster with default indices reports yellow, not green. That is expected and healthy — replicas simply have nowhere to go on one node. yellow is a success state here, never a failure.


🏗 Architecture

┌────────────────────────────── Host Machine (Elementary OS 8) ────────────────────────────────┐
│                                                                                              │
│   ┌──────────────────────────────┐                                                           │
│   │   NestJS Application         │                                                           │
│   │   (Local Process)            │                                                           │
│   │   Runtime: Node.js           │                                                           │
│   └───────────────┬──────────────┘                                                           │
│                   │ HTTP Request                                                             │
│                   │ http://localhost:9200                                                    │
│                   ▼                                                                          │
│   ┌──────────────────────────────────────────────┐                                           │
│   │        Elasticsearch Container               │                                           │
│   │        Image: 8.12.0                         │                                           │
│   │                                              │                                           │
│   │   • Mode: Single Node                        │                                           │
│   │   • Heap: 1GB (Xms=1g, Xmx=1g)               │                                           │
│   │   • Memory Limit: 1.5GB                      │                                           │
│   │   • Port: 9200                               │                                           │
│   └───────────────────────────┬──────────────────┘                                           │
│                               │                                                              │
│                               ▼                                                              │
│                ┌────────────────────────────────┐                                            │
│                │     Persistent Volume          │                                            │
│                │     esdata (NVMe SSD)          │                                            │
│                └────────────────────────────────┘                                            │
│                                                                                              │
│   ┌──────────────────────────────┐                                                           │
│   │        Redis Container       │                                                           │
│   │        (Docker Service)      │                                                           │
│   │                              │                                                           │
│   │   • Max Memory: 128–200MB    │                                                           │
│   │   • Eviction: allkeys-lru    │                                                           │
│   │   • Port: 6379               │                                                           │
│   └──────────────────────────────┘                                                           │
│                                                                                              │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Because NestJS runs on the host, it always uses http://localhost:9200never http://elasticsearch:9200 (that hostname only resolves inside the Docker network).


✅ Requirements

Tool Version (tested) Notes
Docker Engine 24+ (29.x ok) Must be able to run containers
Docker Compose v2 (docker compose) Bundled with modern Docker
Linux kernel any with vm.max_map_count Elementary OS 8 / Ubuntu-based
RAM 16 GB recommended Setup is tuned for this

🚀 Quick Start

# 1. Clone
git clone https://github.com/programmerShinobi/elasticsearch-docker-dev-setup.git
cd elasticsearch-docker-dev-setup

# 2. Prepare the host (sets & persists vm.max_map_count=262144)
./scripts/setup-host.sh
#    └─ or do it manually:
#       sudo sysctl -w vm.max_map_count=262144
#       echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

# 3. Start Elasticsearch
docker compose up -d

# 4. Wait until healthy, then verify
curl http://localhost:9200/_cluster/health?pretty

Or with the bundled Makefile:

make setup   # host prep
make up      # start
make health  # check cluster health
make logs    # follow logs
make down    # stop (keeps data)

Brand new to all this? Read the Beginner's Tutorial first. Full step-by-step walkthrough: docs/SETUP.md


⚙️ Configuration Explained

Every line of docker-compose.yml maps to a requirement:

Setting Value Purpose
image docker.elastic.co/elasticsearch/elasticsearch:8.12.0 Pinned version for reproducibility
discovery.type single-node No clustering, no master election
xpack.security.enabled false Dev mode → plain HTTP, no auth/TLS
xpack.ml.enabled false Drops ML, saves memory
ES_JAVA_OPTS -Xms1g -Xmx1g Fixed 1 GB heap (min == max)
bootstrap.memory_lock true Locks heap in RAM → no swapping
node.processors 4 Sizes thread pools for the 4-CPU cap
ulimits.memlock -1 / -1 Lets memory locking actually work
ulimits.nofile 65536 Avoids "too many open files" as indices grow
mem_limit 1536m Container can never exceed 1.5 GB
cpus 4.0 Caps ES at 4/8 logical CPUs → host stays responsive
ports 127.0.0.1:9200:9200 Loopback only — not exposed to LAN (security is off)
restart unless-stopped Survives reboots / daemon restarts
stop_grace_period 60s Clean shutdown → no shard corruption / RED
logging 10m × 3 Caps container logs so they never fill the disk
volumes esdata: Indices persist across down/up
healthcheck green/yellow Marks container healthy only when usable

Deep dive on memory & responsiveness: docs/PERFORMANCE.md


🔍 Verification

After docker compose up -d, confirm the install is valid:

# 1. Container is up and healthy
docker compose ps

# 2. Service responds with cluster info (JSON)
curl http://localhost:9200

# 3. Cluster health — "status" must be "green" or "yellow"
curl http://localhost:9200/_cluster/health?pretty

# 4. Port 9200 is open on the host
ss -tlnp | grep 9200

A healthy response from step 3 looks like:

{
  "cluster_name" : "docker-cluster",
  "status" : "yellow",
  "number_of_nodes" : 1,
  "active_primary_shards" : 1,
  "unassigned_shards" : 0
}

Complete verification checklist & pass/fail criteria: docs/VERIFICATION.md


🔌 NestJS Integration

Important

Scope: this repository ships the Elasticsearch infrastructure only — the docker-compose.yml and its supporting tooling. The examples/nestjs/ directory is reference material, not a runnable application. It deliberately omits package.json, tsconfig.json, and a bootstrap entrypoint. The files exist to document the one integration detail that trips people up — the localhost vs elasticsearch host rule — and to be copied into your NestJS project, where they compile and run.

Because NestJS runs as a normal host process (outside Docker), it always reaches the published port over localhost:

node: 'http://localhost:9200'      // ✅ correct — host-local process
node: 'http://elasticsearch:9200'  // ❌ resolves only inside the Docker network

Minimal module wiring (env-driven, no auth — security is off in dev mode):

// search.module.ts
import { Module } from '@nestjs/common';
import { ElasticsearchModule } from '@nestjs/elasticsearch';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ElasticsearchModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        // From .env: ELASTICSEARCH_NODE=http://localhost:9200
        node: config.get<string>('ELASTICSEARCH_NODE', 'http://localhost:9200'),
      }),
    }),
  ],
  exports: [ElasticsearchModule],
})
export class SearchModule {}
File Responsibility
search.module.ts Registers the client from ELASTICSEARCH_NODE (with retries & timeout).
search.service.ts Performance-conscious wrapper — single-doc CRUD, bulk index/update/delete, count, paginated & streaming search (PIT + search_after), update/delete-by-query, and index admin. Designed to avoid N+1 loops and deep pagination.
search.controller.ts Demo REST endpoints proving the host → localhost:9200 round trip.

How to use it: install the deps, copy these files into your project, import SearchModule. Step-by-step in examples/nestjs/README.md.


🩺 Performance & Stability

This setup is engineered to keep an i7-1185G7 / 16 GB laptop responsive:

  • Memory ceiling: ES can never use more than 1.5 GB (1 GB heap + overhead).
  • No swap: heap is locked into RAM, so the OS won't page it out.
  • Single node: no inter-node chatter, minimal CPU at idle.
  • Bounded shards: one default index = 1 primary shard, cheap to manage.

Leaves roughly 14 GB for your IDE, browser, NestJS, and Redis.

Tuning notes & what to change if you have less/more RAM: docs/PERFORMANCE.md


🛠 Troubleshooting

Symptom Likely cause Fix
Container exits immediately vm.max_map_count too low Run ./scripts/setup-host.sh
curl: connection refused ES still booting (~30–60s) Wait, watch docker compose logs -f
Status stuck yellow Unassigned replicas (normal) None needed — yellow is healthy here
Status RED Disk full / corrupt data See docs/TROUBLESHOOTING.md
NestJS can't connect Used elasticsearch:9200 Use http://localhost:9200
OOM / killed Other heavy apps Lower heap, see PERFORMANCE.md

Full guide: docs/TROUBLESHOOTING.md


🗂 Project Structure

elasticsearch-docker-dev-setup/
├── docker-compose.yml        # The single-node ES service (the core)
├── .env.example              # ELASTICSEARCH_NODE for the NestJS side
├── Makefile                  # make setup | up | down | health | logs
├── scripts/
│   └── setup-host.sh         # Sets & persists vm.max_map_count
├── examples/
│   └── nestjs/               # Reference snippets to copy into your NestJS app
├── docs/
│   ├── TUTORIAL.md           # Zero-assumptions beginner walkthrough
│   ├── SETUP.md              # Step-by-step install
│   ├── VERIFICATION.md       # Pass/fail acceptance checks
│   ├── PERFORMANCE.md        # Memory & responsiveness tuning
│   └── TROUBLESHOOTING.md    # Common failures & fixes
├── LICENSE
└── README.md

❓ FAQ

Why is the status "yellow" and not "green"?

A single node can't hold replica shards (a replica must live on a different node than its primary). Default indices request 1 replica, so it stays unassigned and the cluster reports yellow. All your data is fully available. This is the correct, healthy state for single-node dev.

Is it safe to use in production?

No. Security is disabled and there is no clustering/replication. This is a development setup only.

Will this touch my existing Redis container?

No. This Compose project only defines the elasticsearch service and its own esdata volume. Redis is untouched.

How do I reset all data?

docker compose down -v (or make clean) removes the esdata volume. Destructive — all indices are deleted.


📄 License

MIT © 2026 programmerShinobi

About

Single-node Elasticsearch on Docker for local NestJS development — stable, lightweight, dev-mode

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors