Day 2: Running OpenSearch Locally
From “docker run” to production-ready configuration in one guide
The Problem Nobody Warns You About
You found a Docker command online. You ran it. OpenSearch started. Victory?
Not quite.
Three days later, you’re debugging why your cluster won’t accept connections, why security is disabled (or worse, why it’s enabled and you can’t authenticate), and why your laptop sounds like a jet engine.
The gap between “OpenSearch is running” and “OpenSearch is running correctly” is where most beginners get stuck.
Today, we bridge that gap.
What We are Building
By the end of this guide, you will have:
A single-node OpenSearch 3.0.0 cluster running in Docker
A multi-container setup with Docker Compose (OpenSearch + Dashboards)
Deep understanding of security parameters (and when to disable them safely)
Performance tuning that won’t melt your laptop
A reusable configuration you can version control
Let’s start from first principles.
Part 1: Docker Basics for OpenSearch
Why Docker?
Before we run commands, let’s understand why Docker matters for OpenSearch:
Without Docker:
Install Java, set JAVA_HOME
Download, extract, configure manually
Manage multiple versions manually
“Works on my machine” problems
Cleanup is messy
With Docker:
One command to run
Switch versions with a tag
Identical everywhere
docker rmand done
Docker gives you isolation and reproducibility. Your OpenSearch environment is the same whether you’re on macOS, Windows, or Linux.
The Simplest Possible Command
Here’s the absolute minimum to get OpenSearch 3.0.0 running:
docker run -d \
--name opensearch-node \
-p 9200:9200 \
-p 9600:9600 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStr0ng#Pass!" \
opensearchproject/opensearch:latest
Let’s decode every piece:
-d Run in background (detached mode)
--name opensearch-node Give the container a memorable name
-p 9200:9200 Expose REST API port (host:container)
-p 9600:9600 Expose Performance Analyzer port
discovery.type=single-node Tell OpenSearch this is a solo node, not a cluster
OPENSEARCH_INITIAL_ADMIN_PASSWORD Set the admin password (required since OpenSearch 2.12)
Verify It’s Working
# Check container is running
docker ps
# Check OpenSearch is responding
curl -k -u admin:MyStr0ng#Pass! https://localhost:9200
The -k flag tells curl to accept the self-signed SSL certificate.
Expected response:
{
"name" : "opensearch-node",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "...",
"version" : {
"distribution" : "opensearch",
"number" : "3.4.0",
...
},
"tagline" : "The OpenSearch Project: https://opensearch.org/"
}
Congratulations — OpenSearch 3.4.0 is running.
But we are just getting started.
Part 2: Understanding Security Parameters
OpenSearch’s security plugin is powerful but can be confusing for beginners. Let’s demystify it.
The Security Plugin: What It Does
The security plugin handles:
Authentication: Who are you? (username/password, certificates, SAML, OIDC)
Authorization: What can you do? (roles, permissions, tenants)
Encryption: TLS/SSL for data in transit
Audit logging: Who did what, when?
By default, security is enabled. This is good for production. For local development, it can be friction.
Option A: Keep Security Enabled (Recommended)
For local development with security:
docker run -d \
--name opensearch-secure \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
opensearchproject/opensearch:latest
Password Requirements (OpenSearch 2.12+):
Minimum 8 characters
At least one uppercase letter
At least one lowercase letter
At least one digit
At least one special character
Connecting with security enabled:
# With curl
curl -k -u admin:Dev#Pass123! https://localhost:9200
# From Python
from opensearchpy import OpenSearch
client = OpenSearch(
hosts=[{'host': 'localhost', 'port': 9200}],
http_auth=('admin', 'Dev#Pass123!'),
use_ssl=True,
verify_certs=False # For self-signed certs
)
Option B: Disable Security (Development Only)
For rapid prototyping, where you need zero friction:
docker run -d \
--name opensearch-insecure \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "DISABLE_SECURITY_PLUGIN=true" \
opensearchproject/opensearch:latest
Connecting without security:
# Simple curl - no auth, no SSL
curl http://localhost:9200
# From Python
client = OpenSearch(
hosts=[{'host': 'localhost', 'port': 9200}],
use_ssl=False
)
When to Disable Security
OK to Disable:
Quick local experiments
Learning and tutorials
Throwaway demos
CI/CD pipeline tests
Keep Security ON:
Any shared environment
Development servers others access
Pre-production testing
Anything with real data
The Rule: If anyone other than you can access it, security stays ON.
Security Environment Variables Reference
OPENSEARCH_INITIAL_ADMIN_PASSWORD Sets admin password on first run. Required if security is enabled.
DISABLE_SECURITY_PLUGIN Turns off security entirely. Default is false.
DISABLE_INSTALL_DEMO_CONFIG Skip demo certificates. Default is false.
plugins.security.ssl.http.enabled Enable HTTPS. Default is true.
plugins.security.disabled Another way to disable security. Default is false.
Part 3: Performance Parameters That Actually Matter
Running OpenSearch on a laptop is different from running it in production. Here’s how to tune it for local development.
The JVM Heap: Most Important Setting
OpenSearch runs on the JVM. The heap size determines how much memory it can use.
The Rule: Set heap to 50% of available RAM, but never more than 32GB (due to compressed pointers).
For local development on a 16GB laptop:
docker run -d \
--name opensearch-tuned \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
-e "OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g" \
opensearchproject/opensearch:latest
Recommended heap sizes by laptop RAM:
8GB RAM Recommended heap: 1-2GB Setting: -Xms1g -Xmx1g
16GB RAM Recommended heap: 2-4GB Setting: -Xms2g -Xmx2g
32GB RAM Recommended heap: 4-8GB Setting: -Xms4g -Xmx4g
Critical: Always set -Xms and -Xmx to the same value. This prevents expensive heap resizing at runtime.
Memory Lock: Prevent Swapping
Swapping is death for search performance. Lock the heap in RAM:
docker run -d \
--name opensearch-locked \
-p 9200:9200 \
--ulimit memlock=-1:-1 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
-e "bootstrap.memory_lock=true" \
opensearchproject/opensearch:latest
Virtual Memory: The mmap Issue
OpenSearch uses memory-mapped files. Linux has a default limit that’s too low.
On your host machine (not in Docker), run:
# Temporary (until reboot)
sudo sysctl -w vm.max_map_count=262144
# Permanent (add to /etc/sysctl.conf)
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
On macOS: Docker Desktop handles this automatically.
On Windows with WSL2: Run in WSL terminal:
wsl -d docker-desktop
sysctl -w vm.max_map_count=262144
Performance Environment Variables Reference
OPENSEARCH_JAVA_OPTS JVM heap settings. Recommended local value: -Xms2g -Xmx2g
bootstrap.memory_lock Prevent swapping. Recommended: true
cluster.routing.allocation.disk.threshold_enabled Disk watermarks. Default: true
indices.memory.index_buffer_size Indexing buffer. Default: 10%
Part 4: Docker Compose — The Right Way
Running multiple containers manually is tedious. Docker Compose defines your entire stack in one file.
Basic docker-compose.yml
version: '3.8'
services:
opensearch:
image: opensearchproject/opensearch:latest
container_name: opensearch
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- opensearch-data:/usr/share/opensearch/data
ports:
- "9200:9200"
- "9600:9600"
networks:
- opensearch-net
dashboards:
image: opensearchproject/opensearch-dashboards:latest
container_name: opensearch-dashboards
environment:
- OPENSEARCH_HOSTS=["https://opensearch:9200"]
ports:
- "5601:5601"
networks:
- opensearch-net
depends_on:
- opensearch
volumes:
opensearch-data:
networks:
opensearch-net:
What Each Section Does
services Defines each container — opensearch is the search engine, dashboards is the web UI (like Kibana)
environment Configuration via environment variables
ulimits System limits for memory lock and file descriptors
volumes Persist data across container restarts — without this, you lose all data when the container stops
ports Expose to host machine
networks Containers on the same network can talk to each other by name
depends_on Start order — dashboards waits for opensearch
Running Docker Compose
# Start everything
docker compose up -d
# View logs
docker compose logs -f
# Stop everything
docker compose down
# Stop and delete data
docker compose down -v
Development vs Production Compose Files
For development without security:
# docker-compose.dev.yml
version: '3.8'
services:
opensearch:
image: opensearchproject/opensearch:latest
container_name: opensearch-dev
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- DISABLE_SECURITY_PLUGIN=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- opensearch-data:/usr/share/opensearch/data
ports:
- "9200:9200"
networks:
- opensearch-net
dashboards:
image: opensearchproject/opensearch-dashboards:latest
container_name: dashboards-dev
environment:
- OPENSEARCH_HOSTS=["http://opensearch:9200"]
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
ports:
- "5601:5601"
networks:
- opensearch-net
depends_on:
- opensearch
volumes:
opensearch-data:
networks:
opensearch-net:
Run with:
docker compose -f docker-compose.dev.yml up -d
Part 5: Multi-Node Cluster (Bonus)
For testing cluster behavior locally:
# docker-compose.cluster.yml
version: '3.8'
services:
opensearch-node1:
image: opensearchproject/opensearch:latest
container_name: opensearch-node1
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node1
- discovery.seed_hosts=opensearch-node1,opensearch-node2
- cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=Cluster#Pass123!
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- opensearch-data1:/usr/share/opensearch/data
ports:
- "9200:9200"
- "9600:9600"
networks:
- opensearch-net
opensearch-node2:
image: opensearchproject/opensearch:latest
container_name: opensearch-node2
environment:
- cluster.name=opensearch-cluster
- node.name=opensearch-node2
- discovery.seed_hosts=opensearch-node1,opensearch-node2
- cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
- bootstrap.memory_lock=true
- OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=Cluster#Pass123!
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- opensearch-data2:/usr/share/opensearch/data
networks:
- opensearch-net
dashboards:
image: opensearchproject/opensearch-dashboards:3.0.0
container_name: opensearch-dashboards
environment:
- OPENSEARCH_HOSTS=["https://opensearch-node1:9200","https://opensearch-node2:9200"]
ports:
- "5601:5601"
networks:
- opensearch-net
depends_on:
- opensearch-node1
- opensearch-node2
volumes:
opensearch-data1:
opensearch-data2:
networks:
opensearch-net:
Key differences from single-node:
Single Node: Uses discovery.type=single-node, no cluster manager config, one volume, 2GB heap minimum
Multi-Node Cluster: Uses discovery.seed_hosts=..., requires cluster.initial_cluster_manager_nodes=..., volume per node, 1GB per node minimum
Part 6: Troubleshooting Common Issues
“OpenSearch didn’t start”
# Check container logs
docker logs opensearch-node
# Common fixes:
# 1. vm.max_map_count too low
sudo sysctl -w vm.max_map_count=262144
# 2. Not enough memory
# Reduce heap: -Xms512m -Xmx512m
# 3. Port already in use
docker ps -a # Find conflicting container
lsof -i :9200 # Find process using port
“Connection refused”
# Is the container running?
docker ps
# Is OpenSearch ready? (check for "green" or "yellow")
curl -k -u admin:Dev#Pass123! https://localhost:9200/_cluster/health
# Check if port is exposed
docker port opensearch-node
“Authentication failed”
# Did you set the password correctly?
# Password must meet complexity requirements
# For fresh start (deletes all data):
docker rm -f opensearch-node
docker volume rm opensearch-data
# Then re-run docker run command
“Dashboards can’t connect to OpenSearch”
# Are they on the same network?
docker network inspect opensearch-net
# Is the URL correct?
# Use container name, not localhost:
# ✅ OPENSEARCH_HOSTS=["https://opensearch:9200"]
# ❌ OPENSEARCH_HOSTS=["https://localhost:9200"]
Quick Reference Card
Essential Commands
# Start single node (OpenSearch 3.0.0)
docker run -d --name os -p 9200:9200 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Pass#123" \
opensearchproject/opensearch:latest
# Check health
curl -k -u admin:Pass#123 https://localhost:9200/_cluster/health?pretty
# Stop and remove
docker rm -f os
# Docker Compose
docker compose up -d
docker compose logs -f opensearch
docker compose down -v
Environment Variables Cheat Sheet
discovery.type=single-node Solo node mode
OPENSEARCH_INITIAL_ADMIN_PASSWORD Admin password (required)
DISABLE_SECURITY_PLUGIN=true No security (dev only)
OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g Heap size
bootstrap.memory_lock=true Prevent swapping
What’s Next: Day 3
Tomorrow we dive into indexing your first document — the actual reason OpenSearch exists.
We’ll cover:
Creating your first index
Understanding mappings
The document lifecycle (create, read, update, delete)
Bulk operations for real-world data
You now have a running, properly configured OpenSearch 3.0.0 environment. Time to put data in it.
Day 2 of 60 | OpenSearch Zero to Hero
Resources:


