How the sausage gets made
- The image is Debian trixie with Wine 11 from WineHQ, pinned to one version (the 64-bit half plus the 32-bit half that MT4’s ancient MetaEditor needs; Debian’s own Wine 10 can’t run MetaTrader 5 build 6304 and later, which bails out with “A debugger has been found running in your system”), Xvfb as a fake display nobody ever looks at, and one Bash script,
entrypoint.sh. Not a single MetaQuotes byte in it. - The toolchain: on the first run the script downloads the MT4 and MT5 installers, checks each one’s Authenticode signature (valid digest, a certificate chain the image’s CA bundle trusts, and a signing certificate whose common name starts with
MetaQuotes; anything else gets refused), and installs both silently under Wine. If the certificate authority’s revocation server is down, it retries three times, then checks everything but revocation and logs a warning withreason=crl_unreachable. The installers default to RoboForex’s; pointMQL_COMPILER_MT4_URLandMQL_COMPILER_MT5_URLat any broker’s or MetaQuotes’ own. The compiler inside is the same either way. - The standard library: the installers don’t actually put it on disk. A terminal unpacks its
MQL4/MQL5directory, standard headers included, the first time it starts. So setup starts each terminal once, headless, waits forTrade/Trade.mqh(MT5) andstdlib.mqh(MT4) to show up, and kills it. A few seconds each, once. - The cache: Wine itself is in the image; the Wine prefix with MT4 and MT5 in it is about 2 GB. Once a terminal is installed and has unpacked its standard library, it gets moved out of
Program Filesinto a plain portable directory (drive_c/terminals/mt4,drive_c/terminals/mt5), so the cached terminals are the exact same kind of thing as one you bring yourself. A cache from an older version gets that move on its next run, no re-download. The prefix lives in the Docker volumemql-compiler-cache, one directory per user ID, so everybody on a box shares a single volume.docker volume rm mql-compiler-cachenukes it and the next run installs again. The container runs as your UID, so the compiled files are yours. - The mounts: the command turns every path into an absolute one and mounts the directory whose
Includeit needs at the same path inside the container: the nearestMQL4above a.mq4, the nearestMQL5above a.mq5, the nearest of either above a directory, or the directory itself or the file’s directory when there’s no such parent. Nested ones collapse into one mount. A--mt4-terminal/--mt5-terminaldirectory is mounted read-only on top. It won’t mount/, so a baremql-compilerfrom/is refused. The container starts in your current directory, so the paths in the output are your real paths. On SELinux hosts, the mounts carry no:zlabel, so expect permission errors there. - The seccomp profile: Wine 11 calls
userfaultfdwhile it starts. Docker’s default seccomp filter doesn’t allow that call, and under Linux 7.0 (Ubuntu 26.04, GitHub’subuntu-latest) Wine then dies withwine: socket : Function not implementedbefore the prefix even exists.seccomp/wine.jsonis Docker’s own default profile (moby/profilesseccomp/v0.3.0, checksum pinned, rebuilt withmake seccompfrom theMakefile) with that one call allowed; the kernel’svm.unprivileged_userfaultfdrule still applies. The installed command, the Action and the tests all pass it todocker run. - Compiling: every file goes through MetaEditor’s command-line compiler with
/incpointed at the merged directory from how includes get resolved. MetaEditor’s exit code is a lying piece of shit (it’s the number of files that compiled, so 1 when everything went fine and 0 when it failed), so the script ignores it, reads theResult:line of the compile log instead, and turns the Windows paths in there back into yours.