Deploy CAPEv2 (Malware Configuration And Payload Extraction) using Docker with KVM/QEMU as the hypervisor.
Inspired by celyrin/cape-docker, reworked for native Linux KVM, multi-service orchestration, and environment-driven configuration.
This project containerizes the CAPEv2 malware analysis sandbox while keeping KVM/libvirt on the host for VM management. The Docker stack handles all supporting services (database, web UI, task queue) and communicates with the host hypervisor through a mounted libvirt socket.
Host (Ubuntu 22.04/24.04)
|
|-- docker compose
| |-- cape-sandbox (analysis engine, --net=host)
| |-- cape-web (Django UI, port 8000)
| |-- cape-guacd (Guacamole daemon, live VNC view of the analysis VM)
| |-- postgresql (task database)
| |-- mongodb (results storage)
| +-- redis (task queue)
|
+-- KVM/libvirt
+-- Windows VM (virbr1, isolated network)
+-- CAPE agent --> ResultServer (port 2042)
cape-guacd (guacamole/guacd) backs the web UI's live VM view: it's the protocol daemon that translates the analysis VM's VNC feed for display in the browser during a running analysis. It runs with network_mode: host and exposes no port of its own.
| celyrin/cape-docker | This project | |
|---|---|---|
| Hypervisor | VirtualBox | KVM/QEMU |
| VM bridge | Custom Go binaries (vbox-server/vbox-client) |
Native libvirt socket |
| Architecture | Monolithic container | Multi-service (5 containers) |
| Configuration | Manual | Automatic via .env |
| Go compiler | Required | Not required |
- Ubuntu 22.04 or 24.04 LTS (bare-metal or nested-virt capable VM)
- Docker Engine + docker compose v2
- A Windows ISO (Win7 SP1 / Win10 / Win11)
KVM, QEMU, and libvirt are installed automatically by setup-host.sh.
sudo bash setup-host.shInstalls KVM/libvirt, creates an isolated network on virbr1, and configures iptables rules.
cp .env.example .envEdit .env and set at minimum:
| Variable | Description |
|---|---|
POSTGRES_PASSWORD |
Database password |
CAPE_SECRET_KEY |
Django secret key |
CAPE_RESULTSERVER_IP |
Host bridge IP reachable by VMs |
VM1_LABEL |
libvirt VM name |
VM1_IP |
Static IP of the analysis VM |
VM1_SNAPSHOT |
Snapshot to restore before each analysis |
CAPE_NETWORK_IFACE |
Host bridge interface used for the analysis network (default: virbr1) |
CAPE_INTERNET_IFACE |
Host interface used if CAPE_DEFAULT_ROUTE=internet; leave unset to keep VMs isolated |
# Interactive guide
python3 scripts/prepare-vm.py --instructions
# Verify KVM sees the VM
python3 scripts/prepare-vm.py --list
# Test agent connectivity
python3 scripts/prepare-vm.py --test-agent <vm-name> <vm-ip>The VM must have:
- A static IP within your analysis subnet
- Windows Defender / Firewall disabled
- The CAPE agent running at startup
- A clean snapshot created after setup
docker compose up -d --buildhttp://<host-ip>:8000
.
|-- docker-compose.yml # Service orchestration
|-- .env.example # Configuration template
|-- Dockerfile # Sandbox image (analysis + KVM)
|-- Dockerfile.web # Web UI image (Django + Gunicorn)
|-- setup-host.sh # One-time host preparation
|-- scripts/
| |-- entrypoint.sh # Sandbox container init
| |-- entrypoint-web.sh # Web container init
| |-- configure-cape.py # Generates CAPE configs from env vars
| |-- prepare-vm.py # VM setup helper
| |-- patch_web.py # Build-time patch: makes init_rooter()/init_routing() non-fatal in cape-web
| +-- patch_rooter.py # Build-time patch: wraps rooter.py's sendto() in try/except
|-- nginx/
| +-- cape.conf # Nginx reverse proxy config
|-- templates/
| +-- analysis/overview/_screenshots.html # Custom override of the CAPE web screenshots view
+-- data/ # Persistent volumes (gitignored)
patch_web.py and patch_rooter.py are applied at image build time (see Dockerfile.web / Dockerfile). Stock CAPEv2 assumes a single bare-metal install where the rooter is always reachable; split across containers, cape-web's init_rooter()/init_routing() and rooter.py's sendto() can throw depending on which container finishes starting first. These patches make those calls non-fatal so the stack comes up cleanly regardless of container start order.
# Submit a sample
docker compose exec cape-sandbox python3 utils/submit.py /path/to/sample.exe
# View logs
docker compose logs -f cape-sandbox
docker compose logs -f cape-web
# Access sandbox shell
docker compose exec cape-sandbox bash
# List VMs from inside the container
docker compose exec cape-sandbox virsh -c qemu:///system list --all
# Rebuild after changes
docker compose down && docker compose up -d --buildAdditional VMs can be defined in .env using the VM2_* through VM9_* prefixes:
VM2_LABEL=win7sp1
VM2_IP=192.168.100.11
VM2_SNAPSHOT=clean
VM2_PLATFORM=windows
VM2_ARCH=x64
VM2_TAGS=win7The configuration script picks them up automatically at container startup.
| Problem | Solution |
|---|---|
| Libvirt socket not found | sudo systemctl start libvirtd on the host |
| VM not responding to ping | Check virsh list --all and virsh start <vm> |
| CAPE agent unreachable | Verify agent.py is running + firewall is off in the VM |
| Permission denied on libvirt | sudo usermod -aG libvirt $USER && newgrp libvirt |
| Database connection refused | Wait for healthchecks or check docker compose ps |
- Change
POSTGRES_PASSWORDandCAPE_SECRET_KEYbefore deploying. - The analysis network is isolated by default (no NAT). VMs can only reach the ResultServer.
- Do not expose port 8000 to the Internet without authentication and TLS.
- Use the provided
nginx/cape.confor a reverse proxy like Traefik for production access.
This project is provided as-is for research and educational purposes.