Wazuh on Docker - Deployment with Docker Compose
Wazuh maintains the official wazuh-docker repository containing pre-built Docker images and Docker Compose configurations for all central components. This deployment method suits rapid provisioning of test environments, labs, and smaller production installations.
Prerequisites
Hardware requirements
| Configuration | CPU | RAM | Disk |
|---|---|---|---|
| Single-node | 4 cores | 8 GB | 50 GB |
| Multi-node (per node) | 4 cores | 4 GB | 50 GB |
Software requirements
- Docker Engine 24.0 or later
- Docker Compose v2 (the
docker composeplugin, not the deprecateddocker-composebinary) - Git for cloning the repository
- 64-bit OS: Linux, macOS, or Windows (via WSL2)
Verify installed versions:
docker --version
docker compose versionPort requirements
| Port | Purpose |
|---|---|
| 443 | Wazuh Dashboard (HTTPS) |
| 1514 | Agent connections |
| 1515 | Agent enrollment |
| 9200 | Wazuh Indexer API |
| 55000 | Wazuh Server API |
Confirm that the listed ports are available on the host.
Clone the repository
Clone the wazuh-docker repository and check out the appropriate version tag:
git clone https://github.com/wazuh/wazuh-docker.git -b v4.14.3
cd wazuh-dockerThe repository contains two primary directories:
single-node/- all components on a single hostmulti-node/- distributed architecture with multiple indexer nodes
Single-node deployment
Generate certificates
Before the first launch, generate TLS certificates for inter-component communication:
cd single-node
docker compose -f generate-indexer-certs.yml run --rm generatorCertificates are stored in config/wazuh_indexer_ssl_certs/ and automatically mounted into the containers.
Start the stack
docker compose up -dWait for all containers to initialize (typically 60-90 seconds):
docker compose psAll three services should report running (healthy):
NAME STATUS
single-node-wazuh.manager-1 Up (healthy)
single-node-wazuh.indexer-1 Up (healthy)
single-node-wazuh.dashboard-1 Up (healthy)Access the Dashboard
Open https://<host-ip>:443 in a browser. Default credentials:
- Username:
admin - Password:
SecretPassword
Change the password after initial login. The procedure is covered in the Environment variables section.
Multi-node deployment
The multi-node configuration deploys a three-node indexer cluster, two manager nodes (master and worker), and one dashboard node. All containers run on the same Docker host, but the architecture mirrors a distributed deployment.
Generate certificates
cd multi-node
docker compose -f generate-indexer-certs.yml run --rm generatorStart the cluster
docker compose up -dVerify the indexer cluster health:
curl -sk -u admin:SecretPassword https://localhost:9200/_cluster/health?prettyExpect status: green and number_of_nodes: 3.
Verify the manager cluster
curl -sk -u wazuh-wui:SecretPassword \
-X POST "https://localhost:55000/security/user/authenticate?raw=true" \
| xargs -I {} curl -sk -H "Authorization: Bearer {}" \
"https://localhost:55000/cluster/nodes?pretty"The output should list two nodes: master and worker.
Custom certificates
For production environments, use certificates issued by your organization’s certificate authority instead of the self-signed ones.
Certificate replacement
Prepare PEM-encoded certificate files:
- CA certificate (
root-ca.pem) - Certificate and key for each indexer node
- Certificate and key for the manager
- Certificate and key for the dashboard
- CA certificate (
Place the files in
config/wazuh_indexer_ssl_certs/Update paths in
docker-compose.ymlif file names differ from the defaultsRestart the stack:
docker compose down
docker compose up -dCertificate requirements
- Format: PEM (Base64-encoded)
- Subject Alternative Name (SAN): must include the node DNS name or IP address
- Key Usage: digitalSignature, keyEncipherment
- Extended Key Usage: serverAuth, clientAuth
Persistent volumes
Docker Compose uses named volumes by default for data persistence. Volumes survive container restarts and recreation.
Volumes in single-node configuration
| Volume | Purpose | Container path |
|---|---|---|
wazuh_api_configuration | Server API configuration | /var/ossec/api/configuration |
wazuh_etc | Manager configuration | /var/ossec/etc |
wazuh_logs | Manager logs | /var/ossec/logs |
wazuh_queue | Event queue | /var/ossec/queue |
wazuh_var_multigroups | Multi-group data | /var/ossec/var/multigroups |
wazuh_integrations | Integration scripts | /var/ossec/integrations |
wazuh_active_response | Active Response scripts | /var/ossec/active-response/bin |
wazuh_agentless | Agentless configuration | /var/ossec/agentless |
wazuh_wodles | Wodles (modules) | /var/ossec/wodles |
wazuh_filebeat_etc | Filebeat configuration | /etc/filebeat |
wazuh_filebeat_var | Filebeat data | /var/lib/filebeat |
wazuh-indexer-data | Indexer data | /var/lib/wazuh-indexer |
Volume backups
# List volumes
docker volume ls | grep wazuh
# Back up the indexer data volume
docker run --rm \
-v single-node_wazuh-indexer-data:/data \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/indexer-data.tar.gz -C /data .Using bind mounts
For explicit control over data locations on the host, replace named volumes with bind mounts in docker-compose.yml:
volumes:
- ./data/wazuh-indexer:/var/lib/wazuh-indexer
- ./data/wazuh-logs:/var/ossec/logsCreate directories in advance and set correct permissions:
mkdir -p data/wazuh-indexer data/wazuh-logs
chown -R 1000:1000 data/wazuh-indexerEnvironment variables
Changing the indexer admin password
The password is set via environment variables in docker-compose.yml:
services:
wazuh.indexer:
environment:
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyNewSecurePassword123!Password requirements:
- Minimum 8 characters
- At least one uppercase letter, one lowercase letter, one digit, and one special character
- Must not contain the username
Key manager environment variables
| Variable | Description | Default |
|---|---|---|
INDEXER_URL | Indexer URL | https://wazuh.indexer:9200 |
INDEXER_USERNAME | Indexer username | admin |
INDEXER_PASSWORD | Indexer password | SecretPassword |
FILEBEAT_SSL_VERIFICATION_MODE | SSL verification | full |
Key dashboard environment variables
| Variable | Description | Default |
|---|---|---|
INDEXER_URL | Indexer URL | https://wazuh.indexer:9200 |
INDEXER_USERNAME | Indexer username | admin |
INDEXER_PASSWORD | Indexer password | SecretPassword |
WAZUH_API_URL | Server API URL | https://wazuh.manager |
API_USERNAME | API username | wazuh-wui |
API_PASSWORD | API password | MyS3cr37P450r.*- |
Mounting custom configuration
Custom ossec.conf
To use a custom manager configuration, create an ossec.conf file and mount it:
services:
wazuh.manager:
volumes:
- ./config/ossec.conf:/var/ossec/etc/ossec.confExtract the default configuration from the container as a starting point:
docker compose cp wazuh.manager:/var/ossec/etc/ossec.conf ./config/ossec.confCustom rules and decoders
Mount directories containing custom rules:
services:
wazuh.manager:
volumes:
- ./config/rules/local_rules.xml:/var/ossec/etc/rules/local_rules.xml
- ./config/decoders/local_decoder.xml:/var/ossec/etc/decoders/local_decoder.xmlFor guidance on writing rules, refer to the Wazuh capabilities section.
Custom integrations
services:
wazuh.manager:
volumes:
- ./config/integrations/custom-integration.py:/var/ossec/integrations/custom-integration.pyAfter configuration changes, restart the manager:
docker compose restart wazuh.managerUpgrading containers
Minor version upgrades
- Stop the current stack:
docker compose down- Update the image tag in
docker-compose.ymlor clone the new repository version:
cd ..
git fetch --tags
git checkout v4.14.3- Start the stack with updated images:
docker compose up -dData in named volumes is preserved across upgrades.
Major version upgrades
When upgrading across major versions (e.g., from 4.x to 5.x):
- Back up all volumes
- Review the migration guide in the official documentation
- Perform indexer data migration if the index structure has changed
Production considerations
Resource limits
Set resource limits in docker-compose.yml for predictable behavior:
services:
wazuh.indexer:
deploy:
resources:
limits:
memory: 4G
cpus: "2.0"
reservations:
memory: 2G
cpus: "1.0"JVM Heap tuning
For the indexer, allocate 50% of the container memory to JVM Heap:
services:
wazuh.indexer:
environment:
- OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2gDo not exceed 32 GB - the JVM loses Compressed OOPs optimizations above this threshold.
Logging
Configure the Docker logging driver for rotation:
services:
wazuh.manager:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "10"Monitoring
Add healthchecks for all services if not already defined:
services:
wazuh.indexer:
healthcheck:
test: ["CMD-SHELL", "curl -sk https://localhost:9200 -u admin:$${OPENSEARCH_INITIAL_ADMIN_PASSWORD} | grep -q 'wazuh-indexer'"]
interval: 30s
timeout: 10s
retries: 5Network security
In production, restrict port access via firewall rules. Do not expose port 9200 (Indexer API) to external networks - indexer access should be limited to the internal Docker network or routed through the Dashboard.
Troubleshooting
Indexer container fails to start
Symptoms: container restarts in a loop; logs show max virtual memory areas vm.max_map_count [65530] is too low.
Solution: increase the limit on the host system:
sudo sysctl -w vm.max_map_count=262144To persist across reboots:
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.confCertificate errors on startup
Symptoms: logs contain ssl_exception or certificate_unknown.
Solution:
- Verify certificate presence:
ls -la config/wazuh_indexer_ssl_certs/- Regenerate certificates:
docker compose -f generate-indexer-certs.yml run --rm generator- Restart the stack:
docker compose down
docker compose up -dDashboard shows “Wazuh API is not reachable”
Symptoms: the dashboard loads but displays an API connection error.
Solution:
- Check the manager status:
docker compose logs wazuh.manager | tail -50Confirm that the API username and password in the dashboard environment variables match the manager configuration
Test API reachability from within the dashboard container:
docker compose exec wazuh.dashboard curl -sk -u wazuh-wui:MyS3cr37P450r.*- \
https://wazuh.manager:55000/manager/info?prettyAgents cannot connect
Symptoms: agents on external hosts fail to enroll.
Solution:
- Confirm that ports 1514 and 1515 are published in
docker-compose.yml:
ports:
- "1514:1514"
- "1515:1515"Check the firewall on the Docker host
When enrolling agents, use the Docker host IP address, not the container internal IP
High disk space consumption
Symptoms: /var/lib/docker fills up rapidly.
Solution:
- Configure index rotation in the indexer via an ISM policy
- Configure Docker log rotation (see the Logging section)
- Periodically clean unused images:
docker image prune -f