Build a cybersecurity practice lab at home
Set up an isolated security lab with DVWA as a target, an attacker box and a packet-capture sensor in Docker. Tested, with legal and isolation rules.
A security practice lab gives you targets you are allowed to break, a box to work from, and logs that show what happened. This guide builds one in Docker in a few minutes: an isolated network, DVWA as the target, an attacker container and a packet-capture sensor. Every step was tested, and the focus is on doing it safely.
Rules before tools
Two rules make a lab a lab and not a problem.
- Only test what you own or have written permission to test. In many countries, accessing a computer without authorization is a crime, for example under the UK Computer Misuse Act 1990. Scanning a neighbor's router, your employer's network or a random website "for practice" is not practice. Public practice platforms and bug bounty programs publish their own scope; stay inside it.
- Keep vulnerable systems isolated. A training target is broken on purpose. The DVWA README says it plainly: "Do not upload it to your hosting provider's public html folder or any Internet facing servers, as they will be compromised." Never bridge a target onto your home LAN, never forward a port to it from your router, and never run it on a cloud VM with a public IP.
What you'll build
lab network 10.66.0.0/24 (internal: no gateway out)
+-----------------+-----------------+-------------------+
| | |
attacker dvwa <-------- sensor db
10.66.0.50 10.66.0.10 (shares dvwa's 10.66.0.11
(netshoot) (web target) network, tcpdump) (MariaDB)
- Target: DVWA, the Damn Vulnerable Web Application, with its MariaDB database.
- Attacker:
nicolaka/netshoot, a small image with nmap, curl and tcpdump. - Sensor: a second netshoot container that shares the target's network namespace and writes everything it sees to a pcap file.
- Isolation: one Docker network with
internal: true.
What you need
- Docker with Compose. This was tested with Docker Desktop on macOS, engine 29.8.1.
- About 1 GB of free disk for the images.
Step 1: Write the Compose file
Create a folder and save this as compose.yml:
name: seclab
networks:
lab:
internal: true # no route to your LAN or the internet
ipam:
config:
- subnet: 10.66.0.0/24
volumes:
dvwa-db:
pcap:
services:
dvwa:
image: ghcr.io/digininja/dvwa:latest
environment:
- DB_SERVER=db
depends_on:
- db
networks:
lab:
ipv4_address: 10.66.0.10
db:
image: docker.io/library/mariadb:10
environment:
- MYSQL_ROOT_PASSWORD=dvwa
- MYSQL_DATABASE=dvwa
- MYSQL_USER=dvwa
- MYSQL_PASSWORD=p@ssw0rd
volumes:
- dvwa-db:/var/lib/mysql
networks:
lab:
ipv4_address: 10.66.0.11
attacker:
image: nicolaka/netshoot
command: sleep infinity
networks:
lab:
ipv4_address: 10.66.0.50
sensor:
image: nicolaka/netshoot
network_mode: "service:dvwa" # sees exactly what the target sees
cap_add:
- NET_ADMIN
- NET_RAW
volumes:
- pcap:/pcap
command: tcpdump -i eth0 -nn -w /pcap/dvwa.pcap
The DVWA and database settings come from DVWA's own compose.yml. Two changes make it a lab: the network is internal, and there is no ports: section. DVWA's file publishes the app on 127.0.0.1:4280; here nothing is published, so you reach the target only from the attacker box.
Step 2: Start the lab
docker compose up -d
docker compose ps --format 'table {{.Service}}\t{{.Status}}'
SERVICE STATUS
attacker Up 14 seconds
db Up 14 seconds
dvwa Up 13 seconds
sensor Up 13 seconds
Step 3: Prove the isolation
Do this before anything else. From the attacker, try to reach the internet:
docker compose exec attacker ping -c 1 -W 2 1.1.1.1
docker compose exec attacker curl -s -m 5 https://example.com
ping: connect: Network unreachable
The curl fails too, with exit code 6 (it can't resolve the host). There is no default route on an internal network, so nothing leaves the lab. Repeat this check whenever you change the file.
Step 4: Reach the target from the attacker
docker compose exec attacker curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://10.66.0.10/
docker compose exec attacker nmap -sT -p 1-1000 10.66.0.10
302 http://10.66.0.10/login.php
Nmap scan report for seclab-dvwa-1.seclab_lab (10.66.0.10)
Host is up (0.00029s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT STATE SERVICE
80/tcp open http
The target answers on port 80 and redirects to its login page. From here, DVWA's own documentation and built-in help pages guide you through its exercises and security levels. This post stops at setup on purpose.
Step 5: Read what the defender sees
Every action in the lab leaves traces. Look at them after each exercise; this is where most of the learning happens.
Web server logs. DVWA's Apache writes access logs to the container output:
docker compose logs dvwa | grep GET
dvwa-1 | 10.66.0.50 - - [14/Oct/2026:00:40:31 +0000] "GET / HTTP/1.1" 302 648 "-" "curl/8.21.0"
dvwa-1 | 10.66.0.50 - - [14/Oct/2026:00:40:31 +0000] "GET /login.php HTTP/1.1" 200 1981 "-" "curl/8.21.0"
Packet capture. Copy the sensor's pcap to your machine and open it in Wireshark, or summarize it with tcpdump. This counts TCP SYN packets by source, which makes a port scan stand out:
docker compose cp sensor:/pcap/dvwa.pcap ./dvwa.pcap
docker run --rm -v "$PWD":/w nicolaka/netshoot sh -c \
"tcpdump -nn -r /w/dvwa.pcap 'tcp[tcpflags] == tcp-syn' 2>/dev/null \
| awk '{print \$3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn"
1002 10.66.0.50
2 10.66.0.10
The 1,000-port scan plus two web requests from the attacker show up as 1,002 connection attempts from one address. The two from 10.66.0.10 are DVWA opening connections to its database. Our post on capturing Docker container traffic covers more ways to capture, including live into Wireshark.
Exercises for the defender side
Treat each session as two jobs: do the exercise, then explain it from the logs alone. A few ideas that need nothing beyond this lab:
- Find the scan. Run a scan, then find it in the pcap without looking at your terminal. Note the start time, the source and how many ports were tried.
- Match logs to packets. Pick one line from the Apache log and find the same request in Wireshark. Compare what each source records and what it misses.
- Write a filter. Build a tcpdump or Wireshark display filter that shows only DVWA's traffic to the database. The sensor sees only DVWA's interface, so traffic sent straight from the attacker to the database never reaches it. Work out where you would put a second sensor to see it.
- Change the noise. Run a slower scan with fewer ports and see if your SYN count still makes it obvious.
- Write it up. A short timeline of "what happened, when, from where, and how I know" is the core skill of incident response.
Going further, safely
- Add a VM target. Whole vulnerable operating systems belong in VMs, not containers, because containers share your host's kernel. Metasploitable3 is built with Packer and Vagrant and is described as built "with a large amount of security vulnerabilities". Metasploitable 2 is an older VM image from Rapid7. Put them on a VirtualBox internal network or host-only network, never bridged. Our Vagrant multi-VM lab shows how to define an internal network.
- Use a dedicated attacker VM. Kali Linux or any Linux distribution works. Give it one adapter on the lab network only.
- Add real detection. Put an IDS such as Suricata or Zeek on the sensor's interface and see which of your actions it flags. Ship logs to one place and write your first detection rule from what you saw.
- Add a firewall. Split the lab into zones and log what crosses them. The DMZ firewall lab with nftables is a good base.
- Snapshot before you start. With VMs, take a snapshot of a clean target, so you can reset it after each exercise.
- Keep secrets out. Don't reuse real passwords, real SSH keys or work files anywhere in the lab.
Clean up
docker compose down -v
This removes the containers, the lab network and both volumes, including the database and the pcap. Delete your local copy of dvwa.pcap when you are done with it.
Draw the lab first
Isothrum's editor is a free browser tool, with no account, for drawing labs like this one: containers from any Docker image, Linux and Windows VMs, switches with VLANs, and firewalls or routers. It exports Docker Compose, Vagrant, Proxmox, Kubernetes and cloud files that you review and run yourself, which is a good habit for a security lab: you see exactly which networks each machine joins before anything starts.