Docker containers Wazuh security monitoring FIM logs Cybersecurity

How to Monitor a Docker Server and Its Containers with Wazuh

Step-by-step technical guide: configure Wazuh to monitor the Docker host and its containers. Docker Listener, container logs, FIM, real alerts, and suspicious docker exec detection.

AI Security
12 min read
Background

If you run a Linux server with Docker, Wazuh can monitor both the host and every container running on it. In this guide you'll see exactly what to configure, which alerts get generated, and how to detect a suspicious interactive access to a container.

What can Wazuh monitor on a Docker server?

When you install the Wazuh agent on the Linux host running Docker, you get four layers of visibility:

  • Docker API events (start, stop, exec, image pulls…) through the docker-listener module
  • Container logs collected from the host filesystem
  • File integrity (FIM) over the Docker configuration: /etc/docker, socket, binaries
  • Image vulnerabilities through integration with Trivy or Grype

All of this without installing any agent inside the containers.

Prerequisites

  • Wazuh Manager 4.7 or later
  • Wazuh agent installed on the Linux host with Docker
  • Docker Engine installed
  • Python 3.8–3.12 on the host

Step 1: Install the Python dependencies for the wodle

The docker-listener module is a Python script that uses the docker library to connect to the daemon API. Without this step the wodle won't start. The exact versions matter:

On Python 3.8–3.10:

pip3 install docker==7.1.0 urllib3==1.26.20 requests==2.32.2

On Python 3.11–3.12 (systems with a managed environment, like Ubuntu 23+):

pip3 install docker==7.1.0 urllib3==1.26.20 requests==2.32.2 --break-system-packages

Verify the installation works:

python3 -c "import docker; c = docker.from_env(); print(c.ping())"
# Should return: True

Step 2: Grant the wazuh user access to the Docker socket

The wodle runs as the wazuh user. So it can access the Docker socket:

sudo usermod -aG docker wazuh

Apply the group change without rebooting:

sudo systemctl restart wazuh-agent

Step 3: Enable the Docker Listener (wodle)

The docker-listener module connects to the Docker API and captures, in real time, every daemon event: container creation, starts, stops, command execution, image pulls, network connections, and more.

Add this to the agent's /var/ossec/etc/ossec.conf:

<wodle name="docker-listener">
  <interval>10m</interval>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
  <disabled>no</disabled>
</wodle>

Once active, this module captures events such as:

  • create, start, stop, die, destroy — container lifecycle
  • exec_create, exec_start — commands run inside a container
  • pull, push, tag, delete — image operations
  • connect, disconnect — network changes
  • commit, export — operations that generate new images

The Wazuh rules for these events are in /var/ossec/ruleset/rules/0560-docker_integration_rules.xml (IDs 87901–87920).

Step 4: Collect the container logs

Docker, with the default driver (json-file), stores each container's logs in:

/var/lib/docker/containers/<ID_CONTENEDOR>/<ID_CONTENEDOR>-json.log

Add this block to ossec.conf so Wazuh reads them:

<localfile>
  <log_format>syslog</log_format>
  <location>/var/lib/docker/containers/*/*-json.log</location>
</localfile>

Use log_format syslog even though the files are JSON. Wazuh will read each full line and the decoders will extract the fields. If you use log_format json, the parser will try to interpret Docker's wrapper (which has log, stream, and time fields) and may break on the actual log content.

If in your environment the containers use the journald driver, the configuration changes:

<localfile>
  <log_format>journald</log_format>
  <location>journald</location>
</localfile>

Step 5: FIM over the Docker configuration

The file integrity module (syscheck) should monitor Docker's configuration files, but not the entire /var/lib/docker directory: it can grow to hundreds of GB and changes constantly with the overlay2 layers, generating thousands of false alerts.

Recommended configuration on the agent:

<syscheck>
  <!-- Docker daemon configuration -->
  <directories realtime="yes" check_all="yes" report_changes="yes">/etc/docker</directories>

  <!-- Docker socket: detect permission changes -->
  <directories realtime="yes" check_all="yes">/run/docker.sock</directories>

  <!-- Docker binaries -->
  <directories check_all="yes">/usr/bin/docker</directories>
  <directories check_all="yes">/lib/systemd/system/docker.service</directories>

  <!-- Exclude data directories (highly volatile) -->
  <ignore>/var/lib/docker/overlay2</ignore>
  <ignore>/var/lib/docker/containers</ignore>
  <ignore>/var/lib/docker/tmp</ignore>
</syscheck>

Any change to /etc/docker/daemon.json or to the socket permissions will generate a FIM alert in Wazuh.

Step 6: Restart the agent and verify

# Restart the agent
sudo systemctl restart wazuh-agent

# Check that the wodle starts without errors
sudo tail -f /var/ossec/logs/ossec.log | grep docker

You should see something like:

INFO: wazuh-modulesd:docker-listener: Module Docker Listener started.
INFO: wazuh-modulesd:docker-listener: Listening to Docker events.

Key alerts you'll see in the dashboard

With this configuration, the Wazuh dashboard will show alerts like these:

Rule IDLevelEvent
879013Container created
879033Container started
879043Container stopped
879075Command run inside a container
879115Container removed with kill
879123Docker image pulled
879185Container turned into an image (commit)

Hands-on case: detecting a suspicious docker exec

The most security-relevant event is when someone runs an interactive shell inside a container. Simulate it like this:

# Spin up a test container
docker run -d --name test_nginx nginx:latest

# Run an interactive shell (this is what we want to detect)
docker exec -it test_nginx /bin/sh

Wazuh captures two events: exec_create and exec_start. The second triggers rule 87907 at level 5. To raise it to level 12 and map it to MITRE ATT&CK T1609, add this custom rule to the manager's /var/ossec/etc/rules/local_rules.xml:

<group name="docker,shell_access">
  <rule id="100110" level="12">
    <if_sid>87907</if_sid>
    <field name="docker.Action">exec_start: (/bin/sh|/bin/bash|/bin/zsh|sh|bash)</field>
    <description>Docker: Interactive shell launched in container $(docker.Actor.Attributes.name)</description>
    <options>no_full_log</options>
    <group>docker_shell,attack,T1609</group>
  </rule>

  <!-- Privileged container: risk of escaping to the host -->
  <rule id="100111" level="14">
    <if_sid>87901</if_sid>
    <field name="docker.Actor.Attributes.privileged">true</field>
    <description>Docker: Privileged container created - $(docker.Actor.Attributes.name)</description>
    <group>docker_privileged,pci_dss_10.6.1</group>
  </rule>
</group>

Apply the changes on the manager:

sudo /var/ossec/bin/wazuh-logtest    # verify the rule has no errors
sudo systemctl restart wazuh-manager

Extra: SCA Docker CIS Benchmark

Wazuh doesn't include the Docker CIS policy by default in its standard installation (unlike the policies for Ubuntu or RHEL), but you can add it manually. The official Wazuh blog publishes a YAML file with the 98 controls of the CIS Docker Benchmark v1.7.0.

Once you have the cis_docker_bench.yml file, enable it in the agent's ossec.conf:

<sca>
  <enabled>yes</enabled>
  <scan_on_start>yes</scan_on_start>
  <interval>12h</interval>
  <skip_nfs>yes</skip_nfs>
  <policies>
    <policy>/var/ossec/etc/sca/cis_docker_bench.yml</policy>
  </policies>
</sca>

This will check things like: whether containers run as root, whether the rootfs is read-only, whether sensitive host volumes are mounted, or whether the Docker socket is exposed inside any container.

Extra: Scanning image vulnerabilities with Trivy

Wazuh's vulnerability-detection module analyzes the agent's OS packages, but not the internal layers of the Docker images. For that it integrates with Trivy:

# Install Trivy on the host
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | \
  gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] \
  https://aquasecurity.github.io/trivy-repo/deb generic main" | \
  sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy

# Scan an image manually
trivy image nginx:latest

You can then use the command wodle to run Trivy periodically and send the results to Wazuh as events, where custom rules will turn them into level 7 (HIGH) or 12 (CRITICAL) alerts.

Full agent configuration

A summary of all the blocks you add to the Docker host's /var/ossec/etc/ossec.conf:

<!-- 1. Docker Listener -->
<wodle name="docker-listener">
  <interval>10m</interval>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
  <disabled>no</disabled>
</wodle>

<!-- 2. Container logs -->
<localfile>
  <log_format>syslog</log_format>
  <location>/var/lib/docker/containers/*/*-json.log</location>
</localfile>

<!-- 3. FIM -->
<syscheck>
  <directories realtime="yes" check_all="yes" report_changes="yes">/etc/docker</directories>
  <directories realtime="yes" check_all="yes">/run/docker.sock</directories>
  <directories check_all="yes">/usr/bin/docker</directories>
  <ignore>/var/lib/docker/overlay2</ignore>
  <ignore>/var/lib/docker/containers</ignore>
  <ignore>/var/lib/docker/tmp</ignore>
</syscheck>

Conclusion

With this configuration you have full visibility over your Docker server: real-time API events, centralized logs from all containers, alerts on any change to the daemon configuration, and immediate detection of suspicious interactive access. All without any agent inside the containers.

If you also add Trivy and the SCA Docker CIS policy, you cover image vulnerabilities and misconfigurations too, before they reach production.

Have Docker in production and want to review how Wazuh is configured in your environment? Book a free consultation and we'll review it together.


Need to implement this solution? Book a free consultation here.

Background