diff --git a/.dockerignore b/.dockerignore
index 8ce14320ca..8b70e3ce1a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -34,6 +34,7 @@ secrets/
Thumbs.db
# CI artifacts and tooling that the image doesn't need
+.hermit/
.cache/
coverage/
dist/
diff --git a/Cargo.lock b/Cargo.lock
index 937ead564a..babd04af23 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -915,6 +915,17 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "buzz-backend-fly"
+version = "0.1.0"
+dependencies = [
+ "hex",
+ "nostr",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+]
+
[[package]]
name = "buzz-backend-kubernetes"
version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index cc1dd0f9df..2f28485116 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ members = [
"crates/buzz-dev-mcp",
"crates/buzz-voice",
"crates/buzz-backend-kubernetes",
+ "crates/buzz-backend-fly",
"examples/countdown-bot",
]
exclude = ["desktop/src-tauri"]
diff --git a/Dockerfile.fly-agent b/Dockerfile.fly-agent
new file mode 100644
index 0000000000..2767ac8195
--- /dev/null
+++ b/Dockerfile.fly-agent
@@ -0,0 +1,42 @@
+# syntax=docker/dockerfile:1.7
+# Buzz cloud-agent image: the pinned Sprig runtime used by isolated Fly agents.
+FROM rust:1.95-alpine3.22@sha256:064dfc925d68d1a63f4fd2871bd7dc6e6ea56692989a487185855d62885d90aa AS builder
+
+RUN apk add --no-cache \
+ build-base \
+ cmake \
+ git \
+ musl-dev \
+ openssl-dev \
+ openssl-libs-static \
+ perl \
+ pkgconf \
+ protoc
+WORKDIR /build
+COPY . .
+RUN cargo build --locked --profile sprig -p sprig \
+ && strip target/sprig/sprig
+
+FROM alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
+
+RUN apk add --no-cache bash ca-certificates curl git su-exec \
+ && adduser -D -h /home/agent agent \
+ && install -d -o agent -g agent /workspace /home/agent \
+ && git config --system gpg.format x509 \
+ && git config --system gpg.x509.program /usr/local/bin/git-sign-nostr \
+ && git config --system commit.gpgSign true \
+ && git config --system tag.gpgSign true
+
+COPY --from=builder --chmod=0755 /build/target/sprig/sprig /usr/local/bin/sprig
+COPY --chmod=0755 scripts/sprig-entrypoint.sh /usr/local/bin/sprig-entrypoint
+COPY --chmod=0755 scripts/fly-agent-entrypoint.sh /usr/local/bin/fly-agent-entrypoint
+RUN for name in \
+ buzz-acp buzz-agent buzz-dev-mcp rg tree buzz \
+ git-credential-nostr git-sign-nostr; do \
+ ln -s sprig "/usr/local/bin/$name"; \
+ done
+
+ENV HOME=/home/agent \
+ PATH=/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin
+WORKDIR /home/agent
+ENTRYPOINT ["/usr/local/bin/fly-agent-entrypoint"]
diff --git a/crates/buzz-backend-fly/Cargo.toml b/crates/buzz-backend-fly/Cargo.toml
new file mode 100644
index 0000000000..486e7351c0
--- /dev/null
+++ b/crates/buzz-backend-fly/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "buzz-backend-fly"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+description = "Fly.io backend provider for Buzz remote agents"
+
+[[bin]]
+name = "buzz-backend-fly"
+path = "src/main.rs"
+
+[dependencies]
+nostr = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+sha2 = { workspace = true }
+hex = { workspace = true }
diff --git a/crates/buzz-backend-fly/src/config.rs b/crates/buzz-backend-fly/src/config.rs
new file mode 100644
index 0000000000..53436663b8
--- /dev/null
+++ b/crates/buzz-backend-fly/src/config.rs
@@ -0,0 +1,223 @@
+pub const DEFAULT_REGION: &str = "ams";
+pub const DEFAULT_IMAGE: &str =
+ "registry.fly.io/buzz-agent-runtime-anneday:pilot-20260804-project-mcp-boundary";
+pub const DEFAULT_VM_SIZE: &str = "shared-cpu-1x";
+pub const DEFAULT_MEMORY_MB: u64 = 1024;
+pub const DEFAULT_VOLUME_GB: u64 = 5;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ProviderConfig {
+ pub organization: String,
+ pub region: String,
+ pub image: String,
+ pub vm_size: String,
+ pub memory_mb: u64,
+ pub volume_gb: u64,
+ pub app_prefix: String,
+}
+
+fn optional_string(value: &serde_json::Value, field: &str) -> Result