Skip to content

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:

  1. copies your project to the VM with rsync (leaving out build/, .dart_tool/ and .git/),
  2. runs flutter pub get and flutter build windows --release there,
  3. 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/kvm should show the device, and your user should be allowed to use it (usually by being in the kvm group). 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.

  • ssh and rsync on the Linux host.

  • The VM helper files from the WinBridge repository. The installed package contains only the winbridge program, so fetch them with Git:

    bash
    git clone https://github.com/Dhiva-Labs/WinBridge.git

Step 1: start the VM ​

  1. Open WinBridge/scripts/vm/docker-compose.yml and read it. Adjust RAM_SIZE, CPU_CORES and DISK_SIZE to what your machine can spare. The file starts with 8 GB of memory, 4 CPU cores and a 64 GB disk.

  2. Set a password for the VM's Windows account and start the VM.

    bash
    cd WinBridge/scripts/vm
    export WINBRIDGE_VM_PASSWORD="$(openssl rand -base64 24)"
    docker compose up -d

    The compose file has no default password. docker compose up refuses to start until WINBRIDGE_VM_PASSWORD is set.

  3. Open http://localhost:8006 in 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 user winbridge.

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 ​

  1. On the Linux host, create an SSH key used only for this VM.

    bash
    ssh-keygen -t ed25519 -f ~/.ssh/id_winbridge_vm -N ""
    cat ~/.ssh/id_winbridge_vm.pub

    Copy the line that cat prints. This is the public half of the key. Do not reuse your everyday SSH key here.

  2. Get WinBridge/scripts/vm/setup-windows.ps1 into the VM. Paste its contents through the web console's clipboard, or share a host folder with the VM through the storage volume in the compose file.

  3. In the VM, open PowerShell as administrator and run the script with your public key.

    powershell
    Set-ExecutionPolicy Bypass -Scope Process -Force
    .\setup-windows.ps1 -PublicKey "ssh-ed25519 AAAA... your-key-here"
  4. Wait for it to finish. It ends by printing flutter --version, which confirms the SDK works.

  5. 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.

  1. Find the SSH port the VM is published on. With the compose file above it is 2222. You can confirm with:

    bash
    docker compose port windows 22
  2. Create the config file and make it readable by you only.

    bash
    mkdir -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.json
  3. Check the whole path, accepting the VM's host key this one time.

    bash
    winbridge vm doctor --accept-new

    A clean run reports ssh and rsync found, the config valid, connectivity, remote flutter and remote rsync all ok, and ends with the vm build path looks ready. Fix anything marked MISSING or FAILED before you go on.

The fields of vm.json:

FieldRequiredMeaning
hostYesName or address of the VM. Letters, digits, ., _ and - only.
portNoSSH port. Defaults to 22.
userYesWindows account to sign in as. Same characters as host.
identityFileNoPath to the private key. ~ is your home folder. Without it, ssh uses its own default key or agent.
remoteWorkspaceYesA folder on the VM that already exists, written with forward slashes. No spaces and no ...
flutterPathNoFull 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 ​

bash
winbridge build path/to/your/flutter/app

WinBridge 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 ​

CommandWhat it checks
winbridge vm statusThe VM answers, and Flutter is installed on it.
winbridge vm doctorLocal 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 run winbridge vm doctor --accept-new once.
  • Keep vm.json private with chmod 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.

Next steps ​