My main SOC lab runs on virtual machines, and VMs are slow to rebuild when you break them, which in a lab is often. So I built a second version of the lab that runs in Docker: a full Wazuh SIEM stack, a monitored target, and an attacker, all from one folder and a couple of commands. The repo is public if you want to build your own: github.com/HomeLab-Raghav/docker-soc-lab. This post covers why containers, what runs where, how I rebuild it, and the honest list of what containers cannot do.
Why Containers for a SOC Lab
Three reasons. Speed: the whole stack comes up in minutes, and I can tear it down and rebuild it the same evening without watching progress bars. Repeatability: the lab is a compose file, not a snowflake VM I configured six months ago and am afraid to touch. Portability: it runs on Docker Desktop with WSL2 on the same Windows machine I use for everything else, so the lab travels with the laptop.
There is a fourth reason that matters for learning. When the lab is code, breaking it is cheap. A detection lab you are scared to break is a detection lab you stop experimenting on.
What Runs Where
The stack is Wazuh 4.9.0 in a single node layout, four containers on one shared bridge network:
- Wazuh manager: receives agent logs, runs the decoders and rules, generates alerts.
- Wazuh indexer: OpenSearch under the hood, stores the alerts and events.
- Wazuh dashboard: where I actually work, hunting and building views.
- Ubuntu 22.04 target: a monitored host with the Wazuh agent enrolled, SSH exposed to the lab network, waiting to be attacked.
Splunk runs as its own container alongside the lab when I am practising SPL. Same logs, second SIEM, so I can compare how the two platforms handle the same events. That container is where the queries in my SPL post get tested.
The attacker is a disposable Kali container on the same network. Disposable is the point. When a simulation ends, the container goes away, and nothing about the attack machine can drift into the next test.
Active Directory is the deliberate exception, and it stays on a VM. A Windows domain controller does not belong in a Linux container, and the Windows container route is a fight I do not need. The DC runs as a Windows Server 2022 virtual machine, the Wazuh agent on it forwards logs into the same manager, and my AD lab build covers that side. Containers where they fit, VMs where they do not.
Bringing It Up
First run needs certificates for the indexer, which the repo generates:
bash
docker compose -f generate-indexer-certs.yml run --rm generator
docker compose up -d
After that, day to day work is two commands:
bash
docker compose ps # what is running
docker compose down # stop everything, volumes keep the data
docker compose down -v # full teardown, data gone, clean slate
The Ubuntu target enrols with the manager the same way a physical machine would, agent key and all. That was a deliberate choice. I wanted the pipeline to be real: raw auth.log on the target, shipped by the agent, decoded by the manager, indexed, then visible in the dashboard. No log files copied by hand, no shortcuts that would not exist in a real environment.
The First Test: Hydra Against the Target
The proof run was a THC-Hydra SSH brute force from the Kali container against the Ubuntu target. The detection side worked end to end. Every failed password in auth.log surfaced in the dashboard, decoded, classified, and mapped to MITRE ATT&CK techniques T1110 and T1021.004 with PCI DSS tags attached.
Then the interesting finding. Not one alert said the words brute force. The individual failure alerts fired, hundreds of them, but the correlation alert I expected, the one that stacks repeated failures into a single high level incident, never appeared. The default ruleset saw every tree and missed the forest. Writing the correlation rule myself fixed it, and understanding why it was missing taught me more about how Wazuh rules chain together than the whole install did. A green stack is not the same thing as working detection, and this lab proved that in its first hour.
What Containers Cannot Do
The honest limits, so nobody builds this expecting the wrong thing:
- No real Windows endpoint telemetry. Sysmon, Windows event forwarding, endpoint agent behaviour on a real workstation: that needs actual Windows machines or VMs. The container lab sees Linux logs well and Windows logs only through the VM side.
- Shared kernel. Containers share the host kernel, so kernel level attacks and some network behaviour do not behave the way they would on separate machines. Packet captures between containers are also cleaner than any real network.
- No persistence realism. Attackers in containers do not survive rebuilds, and neither do the messy leftovers, half removed tools, registry changes, weird scheduled tasks, that make real investigations interesting.
- Resource ceilings. One machine runs everything. A heavy simulation and a full reindex at the same time will make the laptop suffer.
None of that makes the container lab a toy. It makes it a fast iteration environment. I prototype detections there, then validate the important ones against the VM lab, including the runs in my 50 simulation results.
How I Rebuild It
Full rebuild from nothing: clone the repo, generate certs, compose up, enrol the agent, run one Hydra pass to confirm the pipeline. Under an hour, most of it waiting on image pulls the first time. That rebuild drill is part of the point. A SOC analyst who can stand up their own tooling from a cold start understands the tooling differently than one who only ever logged into a finished console.
Next: adding a Suricata container for network detection so the lab sees packets as well as logs, and wiring its alerts into the same Wazuh manager.