Build Flutter apps on a Windows VM
Flutter cannot cross-compile a Windows app from Linux. WinBridge builds it on a Windows virtual machine instead: you set the VM up once, and after that winbridge build works on a Flutter project like on any other.
Why a VM
flutter build windows only runs on Windows, with Visual Studio's C++ tools installed. No Linux compiler can stand in for it.
A virtual machine (VM) is a whole computer running as a program on your computer. WinBridge keeps a Windows VM in the background and talks to it over SSH, a protocol for running commands on another machine securely. For each build it:
- copies your project to the VM with
rsync(leaving outbuild/,.dart_tool/and.git/), - runs
flutter pub getandflutter build windows --releasethere, - copies the finished app back into
build/winbridge/windows-x64/.
What you need
A Linux host with KVM, the kernel's virtualization support.
ls -l /dev/kvmshould show the device, and your user should be allowed to use it (usually by being in thekvmgroup). Without KVM the VM is too slow for a Visual Studio build.Docker and Docker Compose, for the route described here. Any other way of creating a Windows 11 VM also works, as long as the VM is reachable over SSH.
About 25 GB of free disk space for Windows, plus about 15 GB for Visual Studio Build Tools and the Flutter SDK inside the VM.
sshandrsyncon the Linux host.The VM helper files from the WinBridge repository. The installed package contains only the
winbridgeprogram, so fetch them with Git:bashgit clone https://github.com/Dhiva-Labs/WinBridge.git
Step 1: start the VM
Open
WinBridge/scripts/vm/docker-compose.ymland read it. AdjustRAM_SIZE,CPU_CORESandDISK_SIZEto what your machine can spare. The file starts with 8 GB of memory, 4 CPU cores and a 64 GB disk.Set a password for the VM's Windows account and start the VM.
bashcd WinBridge/scripts/vm export WINBRIDGE_VM_PASSWORD="$(openssl rand -base64 24)" docker compose up -dThe compose file has no default password.
docker compose uprefuses to start untilWINBRIDGE_VM_PASSWORDis set.Open
http://localhost:8006in a browser. This is the VM's screen. Windows 11 installs itself on first boot, which takes a while. Wait until you see the desktop, logged in as the userwinbridge.
Good to know
Every port the VM publishes (the web console, Remote Desktop and SSH) is bound to the loopback interface, so the VM can be reached from your own machine only. Keep it that way. If you need it from another computer, use an SSH tunnel.
Step 2: prepare Windows
On the Linux host, create an SSH key used only for this VM.
bashssh-keygen -t ed25519 -f ~/.ssh/id_winbridge_vm -N "" cat ~/.ssh/id_winbridge_vm.pubCopy the line that
catprints. This is the public half of the key. Do not reuse your everyday SSH key here.Get
WinBridge/scripts/vm/setup-windows.ps1into the VM. Paste its contents through the web console's clipboard, or share a host folder with the VM through thestoragevolume in the compose file.In the VM, open PowerShell as administrator and run the script with your public key.
powershellSet-ExecutionPolicy Bypass -Scope Process -Force .\setup-windows.ps1 -PublicKey "ssh-ed25519 AAAA... your-key-here"Wait for it to finish. It ends by printing
flutter --version, which confirms the SDK works.Still in the VM, create the folder WinBridge will copy projects into, for example
C:\winbridge. WinBridge does not create it for you.
The script installs Git for Windows, Visual Studio Build Tools 2022 with the C++ desktop workload, the Flutter SDK (in C:\tools\flutter), rsync, and the OpenSSH server. It authorizes your key and turns password sign-in off for SSH. It is safe to run again: each step checks whether it is already done.
Step 3: tell WinBridge where the VM is
WinBridge reads the connection details from ~/.config/winbridge/vm.json.
Find the SSH port the VM is published on. With the compose file above it is
2222. You can confirm with:bashdocker compose port windows 22Create the config file and make it readable by you only.
bashmkdir -p ~/.config/winbridge cat > ~/.config/winbridge/vm.json <<'JSON' { "host": "localhost", "port": 2222, "user": "winbridge", "identityFile": "~/.ssh/id_winbridge_vm", "remoteWorkspace": "C:/winbridge" } JSON chmod 600 ~/.config/winbridge/vm.jsonCheck the whole path, accepting the VM's host key this one time.
bashwinbridge vm doctor --accept-newA clean run reports
sshandrsyncfound, the configvalid,connectivity,remote flutterandremote rsyncallok, and ends withthe vm build path looks ready.Fix anything markedMISSINGorFAILEDbefore you go on.
The fields of vm.json:
| Field | Required | Meaning |
|---|---|---|
host | Yes | Name or address of the VM. Letters, digits, ., _ and - only. |
port | No | SSH port. Defaults to 22. |
user | Yes | Windows account to sign in as. Same characters as host. |
identityFile | No | Path to the private key. ~ is your home folder. Without it, ssh uses its own default key or agent. |
remoteWorkspace | Yes | A folder on the VM that already exists, written with forward slashes. No spaces and no ... |
flutterPath | No | Full path to flutter on the VM, if it is not on the VM's PATH. |
WinBridge copies each project into project inside remoteWorkspace.
Step 4: build
winbridge build path/to/your/flutter/appWinBridge detects the project by a pubspec.yaml that depends on Flutter and uses the vm toolchain. If the project has no windows/ folder yet, WinBridge creates the Windows platform files on the VM's copy before building.
The result in build/winbridge/windows-x64/ is the complete Release folder: the .exe, its .dll files and the data folder. A Flutter Windows app needs all of them, so ship the whole folder. To build an installer for it, use your own NSIS script: the generated one does not include subfolders such as data.
The first build is slow. Later builds reuse the same folder on the VM and are quicker.
Check the VM at any time
| Command | What it checks |
|---|---|
winbridge vm status | The VM answers, and Flutter is installed on it. |
winbridge vm doctor | Local ssh and rsync, the config file, connectivity, Flutter and rsync on the VM. |
Both accept --accept-new, which trusts a host key seen for the first time. It never accepts a key that has changed.
Security notes
- WinBridge signs in with a key only. It never asks for or sends a password, and it refuses a VM whose host key changed.
- If you rebuild the VM, its host key really is different. Remove the old entry from
~/.ssh/known_hosts(the SSH error names the line) and runwinbridge vm doctor --accept-newonce. - Keep
vm.jsonprivate withchmod 600. WinBridge warns when other users can read it. - Use a VM that exists only for WinBridge builds, not a Windows machine you use for anything else. A project you build can run code on that VM.