Yes, fly.io allows you to expose a UDP port. See the fly.toml [1] in the repo. Make sure the tailscale port is pinned [2] to the exposed port (41641 in that case).
I just tested it again and the connections are made directly (after the first 2,3 packages go via DERP):
tailscale ping fly-ams
pong from fly-ams (100.96.123.32) via DERP(ams) in 15ms
pong from fly-ams (100.96.123.32) via [2604:1380:4601:d605:0:6c3b:eed5:1]:41641 in 12ms
tailscale status
100.96.123.32 fly-ams patte@ linux active; offers exit node; direct [2604:1380:4601:d605:0:6c3b:eed5:1]:41641
100.101.54.36 fly-hkg patte@ linux active; offers exit node; direct [2605:4c40:95:4eed:0:40f0:67b1:1]:41641
I just tested it again and the connections are made directly (after the first 2,3 packages go via DERP):
[1]: https://github.com/patte/fly-tailscale-exit/blob/main/fly.to... [2]: https://github.com/patte/fly-tailscale-exit/blob/main/start....