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

ConfigurationCPURAMDisk
Single-node4 cores8 GB50 GB
Multi-node (per node)4 cores4 GB50 GB

Software requirements

  • Docker Engine 24.0 or later
  • Docker Compose v2 (the docker compose plugin, not the deprecated docker-compose binary)
  • Git for cloning the repository
  • 64-bit OS: Linux, macOS, or Windows (via WSL2)

Verify installed versions:

docker --version
docker compose version

Port requirements

PortPurpose
443Wazuh Dashboard (HTTPS)
1514Agent connections
1515Agent enrollment
9200Wazuh Indexer API
55000Wazuh 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-docker

The repository contains two primary directories:

  • single-node/ - all components on a single host
  • multi-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 generator

Certificates are stored in config/wazuh_indexer_ssl_certs/ and automatically mounted into the containers.

Start the stack

docker compose up -d

Wait for all containers to initialize (typically 60-90 seconds):

docker compose ps

All 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 generator

Start the cluster

docker compose up -d

Verify the indexer cluster health:

curl -sk -u admin:SecretPassword https://localhost:9200/_cluster/health?pretty

Expect 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

  1. 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
  2. Place the files in config/wazuh_indexer_ssl_certs/

  3. Update paths in docker-compose.yml if file names differ from the defaults

  4. Restart the stack:

docker compose down
docker compose up -d

Certificate 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

VolumePurposeContainer path
wazuh_api_configurationServer API configuration/var/ossec/api/configuration
wazuh_etcManager configuration/var/ossec/etc
wazuh_logsManager logs/var/ossec/logs
wazuh_queueEvent queue/var/ossec/queue
wazuh_var_multigroupsMulti-group data/var/ossec/var/multigroups
wazuh_integrationsIntegration scripts/var/ossec/integrations
wazuh_active_responseActive Response scripts/var/ossec/active-response/bin
wazuh_agentlessAgentless configuration/var/ossec/agentless
wazuh_wodlesWodles (modules)/var/ossec/wodles
wazuh_filebeat_etcFilebeat configuration/etc/filebeat
wazuh_filebeat_varFilebeat data/var/lib/filebeat
wazuh-indexer-dataIndexer 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/logs

Create directories in advance and set correct permissions:

mkdir -p data/wazuh-indexer data/wazuh-logs
chown -R 1000:1000 data/wazuh-indexer

Environment 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

VariableDescriptionDefault
INDEXER_URLIndexer URLhttps://wazuh.indexer:9200
INDEXER_USERNAMEIndexer usernameadmin
INDEXER_PASSWORDIndexer passwordSecretPassword
FILEBEAT_SSL_VERIFICATION_MODESSL verificationfull

Key dashboard environment variables

VariableDescriptionDefault
INDEXER_URLIndexer URLhttps://wazuh.indexer:9200
INDEXER_USERNAMEIndexer usernameadmin
INDEXER_PASSWORDIndexer passwordSecretPassword
WAZUH_API_URLServer API URLhttps://wazuh.manager
API_USERNAMEAPI usernamewazuh-wui
API_PASSWORDAPI passwordMyS3cr37P450r.*-

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.conf

Extract the default configuration from the container as a starting point:

docker compose cp wazuh.manager:/var/ossec/etc/ossec.conf ./config/ossec.conf

Custom 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.xml

For 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.py

After configuration changes, restart the manager:

docker compose restart wazuh.manager

Upgrading containers

Minor version upgrades

  1. Stop the current stack:
docker compose down
  1. Update the image tag in docker-compose.yml or clone the new repository version:
cd ..
git fetch --tags
git checkout v4.14.3
  1. Start the stack with updated images:
docker compose up -d

Data in named volumes is preserved across upgrades.

Major version upgrades

When upgrading across major versions (e.g., from 4.x to 5.x):

  1. Back up all volumes
  2. Review the migration guide in the official documentation
  3. 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 -Xmx2g

Do 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: 5

Network 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=262144

To persist across reboots:

echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

Certificate errors on startup

Symptoms: logs contain ssl_exception or certificate_unknown.

Solution:

  1. Verify certificate presence:
ls -la config/wazuh_indexer_ssl_certs/
  1. Regenerate certificates:
docker compose -f generate-indexer-certs.yml run --rm generator
  1. Restart the stack:
docker compose down
docker compose up -d

Dashboard shows “Wazuh API is not reachable”

Symptoms: the dashboard loads but displays an API connection error.

Solution:

  1. Check the manager status:
docker compose logs wazuh.manager | tail -50
  1. Confirm that the API username and password in the dashboard environment variables match the manager configuration

  2. 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?pretty

Agents cannot connect

Symptoms: agents on external hosts fail to enroll.

Solution:

  1. Confirm that ports 1514 and 1515 are published in docker-compose.yml:
ports:
  - "1514:1514"
  - "1515:1515"
  1. Check the firewall on the Docker host

  2. 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:

  1. Configure index rotation in the indexer via an ISM policy
  2. Configure Docker log rotation (see the Logging section)
  3. Periodically clean unused images:
docker image prune -f

Additional resources

Last updated on