Bundling a CLI Binary as a Tauri v2 Sidecar: Lessons from Building a Desktop App
When you build a desktop app with Tauri v2 , sooner or later you'll hit a question: how do I bundle and manage an external CLI binary inside my app? Maybe it's ffmpeg for video processing. Maybe it's a database engine. Maybe — as in my case — it's frpc , the reverse-proxy client from the popular frp project. This post walks through the full lifecycle: bundling, spawning, lifecycle management, and even self-updating the binary at runtime — all from Rust. 1. Declaring the Sidecar In tauri.conf.json , declare the binary under bundle.externalBin : { "bundle" : { "externalBin" : [ "binaries/frpc" ] } } Tauri identifies the target platform by a filename suffix convention . You need to place the correctly-named binary in your project: Platform Filename macOS (Apple Silicon) frpc-aarch64-apple-darwin macOS (Intel) frpc-x86_64-apple-darwin Windows (x64) frpc-x86_64-pc-windows-msvc.exe Tauri automatically strips the suffix at runtime and loads the right binary for the current platform. 2. Spawning the Process Use tauri_plugin_shell to spawn the sidecar: use tauri_plugin_shell ::{ ShellExt , process :: CommandEvent }; #[tauri::command] async fn start_frpc ( app : tauri :: AppHandle ) -> Result < (), String > { let sidecar = app .shell () .sidecar ( "frpc" ) .map_err (| e | e .to_string ()) ? ; let ( mut rx , child ) = sidecar .args ([ "-c" , "frpc.toml" ]) .spawn () .map_err (| e | e .to_string ()) ? ; // Store the child handle so we can kill it later app .state :: < std :: sync :: Mutex < Option < tauri_plugin_shell :: process :: CommandChild >>> () .lock () .unwrap () .replace ( child ); // Listen to stdout/stderr in a background task tauri :: async_runtime :: spawn ( async move { while let Some ( event ) = rx .recv () .await { match event { CommandEvent :: Stdout ( line ) => { // Parse log line, update UI state... } CommandEvent :: Stderr ( line ) => { /* ... */ } CommandEvent :: Terminated ( _ ) => { // Process exited — update state machine } _ => {} } } }); Ok (()) } The