One VM eventually runs out of CPU, RAM, disk, or patience. This is how you spread terminals across several Windows VMs, pin the heavy fuckers to NUMA nodes, and keep hot storage away from bulk junk.
vms.yaml(copyvms.yaml.example) declares each VM's resources (cpuset, RAM, CPU cores, disk size, storage path, noVNC port, wickworks sidecar name).config/config.yamlgains avmfield on each terminal entry, binding that terminal to a specific VM.scripts/config_helper.pyreads both files to generate nginx routes targeting the correct container (proxy_pass http://mt5:<port>vshttp://mt5-b:<port>).run.shloops over all VMs for DNAT/iptables setup and auto-generates per-VM group files.docker-compose.yml.j2renders the compose file fromvms.yamlviaconfig_helper.py generate_compose.
- No
vms.yaml→ single-VM mode. All terminals route to the defaultmt5container. The example file is documentation only and does not opt into multi-VM generation. - No
vmfield on a terminal → defaults todefault, routes tomt5.
- mt5-httpapi v4.10.0+
- Linux host with KVM (
/dev/kvm), enough RAM for N VMs, and CPU cores you can pin
Copy the example instead of writing this YAML from memory:
cp vms.yaml.example vms.yamlDefine one entry per VM. The name is how terminals reference it in config.yaml.
vms:
- name: fast
service: mt5
container_name: mt5
cpuset: "0-3"
ram: "8G"
cpu_cores: 4
disk_size: "64G"
storage: /data/mt5-vm-a/storage
log_dir: /data/mt5-shared/logs
novnc_port: 8006
wickworks_service: wickworks
mem_limit: 10G
memswap_limit: 12G
- name: bulk
service: mt5-b
container_name: mt5-b
cpuset: "4-7"
ram: "8G"
cpu_cores: 4
disk_size: "64G"
storage: /data/mt5-vm-b/storage
log_dir: /data/mt5-vm-b/logs
novnc_port: 8007
wickworks_service: wickworks-b
mem_limit: 10G
memswap_limit: 12GFields:
| Field | Required | Description |
|---|---|---|
name |
yes | VM identifier, referenced by terminals[].vm in config.yaml |
service |
yes | Docker compose service name (e.g. mt5, mt5-b) |
container_name |
yes | Docker container hostname for nginx routing |
cpuset |
no | CPU pinning (docker compose cpuset) |
ram |
yes | RAM for the Windows VM (e.g. "112G", "4G") |
cpu_cores |
yes | vCPU count |
disk_size |
yes | VM disk size (e.g. "300G") |
storage |
yes | Host path for VM system disk |
log_dir |
no | Host path mounted at /shared/logs; omit to use the compose template's default log storage |
novnc_port |
no | Host port for noVNC (e.g. 8006, 8007) |
wickworks_service |
no | Name of the wickworks sidecar service |
mem_limit |
no | Docker memory limit (default 116G) |
memswap_limit |
no | Docker mem+swap limit (default 120G) |
extra_binds |
no | Additional host→container bind mounts (hot-tier terminal dirs) |
Add vm: <name> to each terminal in config/config.yaml:
terminals:
- broker: darwinex
account: live
port: 6551
vm: fast
mode: live
- broker: blackbull
account: live-prime
port: 6546
vm: bulk
mode: backtestOn first run, run.sh detects a real vms.yaml alongside docker-compose.yml.j2 and generates the compose file automatically. Without vms.yaml, it copies the single-VM docker-compose.yml.example. To regenerate a configured multi-VM compose file:
python3 scripts/config_helper.py generate_composeThis shits out one service block per VM and wickworks sidecar, plus the shared log rotator, MCP unifier, and nginx router.
run.sh auto-generates these from the active vms.yaml + config.yaml:
| File | Contents |
|---|---|
data/vm-group-<name>.txt |
Terminal filter for each VM (broker + account + instance lines) |
.data/nginx/nginx.conf |
nginx routes with per-terminal proxy_pass to the owning VM's container |
- Add a new entry to
vms.yaml(copy fromvms.yaml.example) with a uniquename,service, andcontainer_name. - Assign some terminals to it via
vm: <new-name>inconfig.yaml. - Regenerate the compose file.
docker compose up -d— nginx routes terminals to the new container automatically.
- Nginx 502 for a terminal: check the generated nginx.conf at
.data/nginx/nginx.conf. Theproxy_passshould target the correct container name for that terminal's VM. - VM not booting: verify
vms.yamlhas correctcpuset(don't overlap pins) and enough host RAM for all VMs combined. Start fromvms.yaml.example. - Port conflicts: each VM needs a unique
novnc_port. Default single-VM is8006; add VMs on8007,8008, etc.