Skip to content

INTENTIUS/mudflaps

Repository files navigation

mudflaps

A standalone, stateful local emulator of the Fly.io Machines API (flaps). Like LocalStack, but for Fly Machines.

mudflaps is the local target for chant's Fly lexicon — its flyApply applier deploys against mudflaps for offline, accountless runs. See the chant docs and the Fly deploy tutorial it powers.

CI Go Reference License: MIT Release GHCR

Purpose

Testing a Machines-API client, such as an infrastructure-as-code applier, requires testing against state: a machine that moves through creating to started, a lease held by another caller, an update that mints a new instance_id and retires the prior version. A schema mock (Prism or WireMock pointed at the OpenAPI spec) validates request and response shapes but holds no state, so it cannot model these behaviors. mudflaps maintains apps, machines, leases, and version history in memory and advances machines through their lifecycle over short, deterministic delays, providing a stateful target for client test suites.

superfly/fly-go is the wire-shape reference; JSON field parity with it is the fidelity contract.

Features

  • Stateful in-memory store of apps and machines with full CRUD.
  • Asynchronous lifecycle state machine on an injected clock: transient states (creating, starting, stopping, restarting, suspending, replacing, destroying) settle into resting states.
  • Suspend/resume, cordon/uncordon, and machine metadata endpoints.
  • Machine leases with nonce, TTL, owner, and expiry; a mutation by a non-holder returns 409, and a conflicting acquire returns the holder's lease envelope (without the nonce).
  • Synchronous version churn: an update mints a new instance_id, marks the prior version replaced, and returns the new version in the response.
  • Deploy-failure injection: a machine whose config.image is the sentinel mudflaps/unpullable settles into failed instead of started, modeling a boot-time image-pull failure ("unable to pull image, canceling deploy"). A client that waits for started observes a deploy that never comes up — its failure path, tested offline.
  • /wait long-poll that blocks until a target state or returns 408; the deadline is measured on the injected clock, so timeouts are deterministic in tests.
  • A /_mudflaps/health endpoint that reports the implemented paths; the roadmap is currently empty — every documented endpoint mudflaps targets is built.
  • Single static binary and distroless container image; no runtime dependencies.

Quick start

Run the container:

docker run --rm -p 4280:4280 ghcr.io/intentius/mudflaps:latest

Or install with Go:

go install github.com/intentius/mudflaps/cmd/mudflaps@latest
mudflaps            # listens on :4280 by default

Point a Machines-API client at it with the same environment variable the real client uses:

export FLY_FLAPS_BASE_URL=http://localhost:4280

The listen address is configurable with -addr or MUDFLAPS_ADDR (default :4280, matching Fly's internal _api.internal:4280).

Usage example

BASE=http://localhost:4280

# Create an app.
curl -s -X POST "$BASE/v1/apps" \
  -d '{"app_name":"demo","org_slug":"personal"}'

# Create a machine.
MID=$(curl -s -X POST "$BASE/v1/apps/demo/machines" \
  -d '{"region":"local","config":{"image":"nginx"}}' | jq -r .id)

# Block until it reaches "started".
curl -s "$BASE/v1/apps/demo/machines/$MID/wait?state=started"
# => {"ok":true}

# Take a lease, then observe a mutation without the nonce being rejected.
curl -s -X POST "$BASE/v1/apps/demo/machines/$MID/lease" -d '{"ttl":30}'
curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST "$BASE/v1/apps/demo/machines/$MID/stop"
# => 409

Comparison

Capability mudflaps Prism / WireMock Real Fly
State machine (create → started, stop, suspend, destroy) Yes No Yes
Leases and nonce conflicts Yes No Yes
Version churn (new instance_id, old replaced) Yes No Yes
Machine metadata (ownership markers) Yes No Yes
Runs fully offline Yes Yes No
Cost Free Free Billed
Real microVMs, images, networking No No Yes

API coverage

Implemented: the apps and machines endpoints (start, stop, restart, suspend, cordon/uncordon, metadata, signal, exec, ps), /wait, the lease endpoints, and volumes, secrets (apply-only), IP assignments, and certificates. The roadmap is empty — every documented endpoint mudflaps targets is built, so /_mudflaps/health reports an empty unimplemented list. The full table is in the API coverage docs.

Roadmap

mudflaps is the local target for the chant fly lexicon's flyApply applier. Tracking epic: chant #736. The Machines surface an IaC applier exercises is complete; new endpoints are staged here as fly-go / the OpenAPI grows.

Development

The primary task runner is just; a Makefile mirrors the same targets.

just build      # compile
just test       # go test ./...
just race       # go test -race ./...
just lint       # golangci-lint
just cover      # coverage profile
just docs-serve # preview the doc site

Contributing

Contributions are welcome. See CONTRIBUTING.md and the Code of Conduct.

License

Licensed under the MIT License. Copyright (c) 2026 Intentius.

About

Stateful local emulator of the Fly.io Machines API (flaps) — like LocalStack, for Fly Machines. Test Machines-API clients offline.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages