docker compose multi-node
services:
es-master:
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0
container_name: es-master
environment:
- node.name=es-master
- cluster.name=elastic-cluster
- cluster.initial_master_nodes=es-master
- discovery.seed_hosts=es-hot,es-warm
- node.roles=[ master ]
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=true
- ES_JAVA_OPTS=-Xms1g -Xmx1g
volumes:
- /opt/elastic/master_data:/usr/share/elasticsearch/data
networks: [elastic]
es-hot:
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0
container_name: es-hot
depends_on: [es-master]
environment:
- node.name=es-hot
- cluster.name=elastic-cluster
- discovery.seed_hosts=es-master
- node.roles=[ data_hot, data_content, ingest ]
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=true
- ES_JAVA_OPTS=-Xms2g -Xmx2g
deploy:
resources:
limits: { cpus: '2.0', memory: 4GB }
volumes:
- /opt/elastic/hot_data:/usr/share/elasticsearch/data
networks: [elastic]
es-warm:
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0
container_name: es-warm
depends_on: [es-master]
environment:
- node.name=es-warm
- cluster.name=elastic-cluster
- discovery.seed_hosts=es-master
- node.roles=[ data_warm ]
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=true
- ES_JAVA_OPTS=-Xms2g -Xmx2g
deploy:
resources:
limits: { cpus: '1.0', memory: 4GB }
volumes:
- /opt/elastic/warm_data:/usr/share/elasticsearch/data
networks: [elastic]
kibana:
image: docker.elastic.co/kibana/kibana:9.0.0
container_name: kibana
depends_on: { es-master: { condition: service_healthy } }
environment:
- ELASTICSEARCH_HOSTS=http://es-hot:9200
- ELASTICSEARCH_SERVICEACCOUNT_TOKEN=${KIBANA_SERVICE_TOKEN}
- XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${ENCRYPTION_KEY}
ports: [ "5601:5601" ]
networks: [elastic]
fleet-server:
image: docker.elastic.co/beats/elastic-agent:9.0.0
container_name: fleet-server
user: root
depends_on: { es-hot: { condition: service_healthy } }
environment:
- FLEET_SERVER_ENABLE=true
- FLEET_SERVER_ELASTICSEARCH_HOST=http://es-hot:9200
- FLEET_SERVER_SERVICE_TOKEN=${FLEET_SERVICE_TOKEN}
- FLEET_SERVER_ELASTICSEARCH_CA_TRUSTED_FINGERPRINT=${CA_FINGERPRINT}
- FLEET_URL=http://fleet-server:8220
- PATH_DATA=/usr/share/elastic-agent/state/data
volumes:
- /opt/elastic/fleet_state:/usr/share/elastic-agent/state
- /var/run/docker.sock:/var/run/docker.sock:ro
networks: [elastic]
networks:
elastic:
driver: bridge
docker compose single-node
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=true
- xpack.security.authc.api_key.enabled=true
- ES_JAVA_OPTS=-Xms2g -Xmx2g
deploy:
resources:
limits:
cpus: '2.0'
memory: 4GB
healthcheck:
test: ["CMD-SHELL", "curl -s -u elastic:${ELASTIC_PASSWORD} http://localhost:9200/_cluster/health | grep -vq '\"status\":\"red\"'"]
interval: 10s
timeout: 10s
retries: 10
volumes:
- /opt/elastic/es_data:/usr/share/elasticsearch/data
networks:
- elastic
kibana:
image: docker.elastic.co/kibana/kibana:9.0.0
container_name: kibana
depends_on:
elasticsearch:
condition: service_healthy
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_SERVICEACCOUNT_TOKEN=${KIBANA_SERVICE_TOKEN}
- XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${ENCRYPTION_KEY}
- NODE_OPTIONS="--max-old-space-size=2048"
ports:
- "5601:5601"
networks:
- elastic
fleet-server:
image: docker.elastic.co/beats/elastic-agent:9.0.0
container_name: fleet-server
user: root
restart: always
depends_on:
elasticsearch:
condition: service_healthy
environment:
- FLEET_SERVER_ENABLE=true
- FLEET_SERVER_ELASTICSEARCH_HOST=http://elasticsearch:9200
- FLEET_SERVER_SERVICE_TOKEN=${FLEET_SERVICE_TOKEN}
- FLEET_SERVER_ELASTICSEARCH_CA_TRUSTED_FINGERPRINT=${CA_FINGERPRINT}
- FLEET_URL=http://fleet-server:8220
- PATH_DATA=/usr/share/elastic-agent/state/data
deploy:
resources:
limits:
cpus: '1.0'
memory: 2GB # Extra headroom for CrowdStrike telemetry processing
volumes:
- /opt/elastic/fleet_state:/usr/share/elastic-agent/state
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- elastic
networks:
elastic:
driver: bridge
install script
#!/bin/bash
set -e
# 1. Environment Variables
ELASTIC_PWD=$(openssl rand -base64 16)
ENCRYPT_KEY=$(openssl rand -base64 32)
# 2. Prepare Host Directories
sudo mkdir -p /opt/elastic/{master_data,hot_data,warm_data,fleet_state}
sudo chown -R 1000:1000 /opt/elastic/*data
sudo chown -R root:root /opt/elastic/fleet_state
# 3. Start Core Elasticsearch
ELASTIC_PASSWORD=$ELASTIC_PWD docker-compose up -d es-master es-hot es-warm
echo "Waiting for Cluster Health..."
until [ "$(docker exec es-master curl -s -u elastic:$ELASTIC_PWD http://localhost:9200/_cluster/health | grep -oP '(?<="status":")[^"]*')" == "green" ] || [ "$(docker exec es-master curl -s -u elastic:$ELASTIC_PWD http://localhost:9200/_cluster/health | grep -oP '(?<="status":")[^"]*')" == "yellow" ]; do
sleep 5
done
# 4. Generate Security Artifacts
FINGERPRINT=$(docker exec es-master openssl x509 -fingerprint -sha256 -noout -in /usr/share/elasticsearch/config/certs/http_ca.crt | cut -d'=' -f2)
KIBANA_TOKEN=$(docker exec es-master bin/elasticsearch-service-tokens create elastic/kibana kibana-token | awk '{print $4}')
FLEET_TOKEN=$(docker exec es-master bin/elasticsearch-service-tokens create elastic/fleet-server fleet-token | awk '{print $4}')
# 5. Build .env
cat <<EOF > .env
ELASTIC_PASSWORD=$ELASTIC_PWD
ENCRYPTION_KEY=$ENCRYPT_KEY
CA_FINGERPRINT=$FINGERPRINT
KIBANA_SERVICE_TOKEN=$KIBANA_TOKEN
FLEET_SERVICE_TOKEN=$FLEET_TOKEN
EOF
# 6. Start UI and Fleet
docker-compose up -d kibana fleet-server
# 7. Final Step: Keystore Injection for CrowdStrike
echo "--------------------------------------------------"
read -p "Enter CrowdStrike API Key: " CS_KEY
docker exec -it fleet-server elastic-agent keystore add CS_SECRET --value "$CS_KEY" --force
echo "Setup Complete! Kibana: http://localhost:5601"
echo "Elastic Password (saved in .env): $ELASTIC_PWD"
PUT _ilm/policy/7day_hot_3month_warm_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "7d",
"max_size": "50gb"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "0ms",
"actions": {
"set_priority": { "priority": 50 },
"forcemerge": { "max_num_segments": 1 },
"shrink": { "number_of_shards": 1 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}