Fly Machines run your app — and your app receives each article.
Fly.io has no content API to publish into — the Fly Machines API manages apps, machines, volumes, certificates and tokens, and nothing in that list can hold an article. Your app on Fly.io receives every article automatically through a signed webhook: one POST route in the image your Machines run, checked against a secret set with fly secrets set. Whether fly launch generated your Dockerfile or you wrote your own, the receiver ships with your next fly deploy.
| Fly.io content API | None — apps, Machines, volumes — the Machines API has no post, page or article resource |
|---|---|
| Delivery target | The app inside your image — one POST route, HMAC checked over ${timestamp}.${rawBody} |
| Stacks | fly launch writes the Dockerfile — Phoenix, Rails, Django, Laravel, Go, Axum and more; bring your own for Spring or Ktor |
| internal_port and 0.0.0.0 | Must match your listener — Fly sets no PORT; get either wrong and deliveries never arrive |
| Stopped Machines | Idle Machines stop — min_machines_running = 1 keeps one up for a Java, Kotlin or .NET receiver |
Who does what
Paste the recipe for the stack in your image, set the secret with fly secrets set, and confirm internal_port matches the port you listen on. After the test delivery turns green there is nothing left to do per article, and your layouts and routes are never touched.
Machine disks reset on every deploy — keep posts in Postgres.
Setup
Fly already runs your stack as a Docker image, so connecting comes down to a route, a secret and one fly.toml value.
Connect a site → Fly.io and choose the stack inside your image: Elixir / Phoenix, Ruby on Rails, Django, FastAPI, Laravel, Go, Rust / Axum and more. That stack’s recipe opens next to your signing secret.
~1 minute
Paste the recipe and listen on 0.0.0.0, not localhost. In fly.toml, internal_port must equal the port you listen on — 8080 in the recipes, since Fly sets no PORT of its own. If fly deploy warns "The app is not listening on the expected address", fix that first.
~10 minutes
Run fly secrets set DRAFTSEO_WEBHOOK_SECRET="…" and Fly restarts your Machines with the value. Keep it out of [env] in fly.toml, which is for settings that are not secret.
~1 minute
Register the https:// URL — force_https answers http:// with a 301, which DraftSEO does not follow. DraftSEO then sends a signed test delivery — and a deliberately invalid one, which your receiver must reject.
~1 minute
Scheduled articles now travel to your Machines without a click from you, each with its images and SEO fields.
Ongoing · zero clicks
FAQ
No — Fly.io holds nothing an article could be published into. The Fly Machines API manages apps, machines, volumes, certificates and tokens, and there is no post, page or article resource among them. DraftSEO publishes into the app your Machines run, and that app is what renders the blog.
Everything on Fly runs as a Docker image, and fly launch generates the Dockerfile for Next.js, Node.js / Express, Astro, SvelteKit, Nuxt (Vue), Laravel, Django, FastAPI, Ruby on Rails, Go, ASP.NET Core, Rust / Axum and Elixir / Phoenix. Expo, plain PHP, Java / Spring and Kotlin (Ktor) have no fly launch scanner, so bring your own Dockerfile — fly deploy builds it — and keep internal_port at 8080 to match the recipes.
Not as it stands. A static site on Fly is still a web-server container, but it has no code of its own to verify and store a delivery. Deploy the receiver as its own small Fly app — the Node.js recipe works as-is — and point the DraftSEO endpoint at it.
The port and the bind address. internal_port in fly.toml has to match the port your app listens on (8080 in the recipes), and the app must listen on 0.0.0.0 rather than localhost; fly deploy warns "The app is not listening on the expected address" when it does not. Then check the endpoint uses https:// — force_https turns http:// into a 301, and DraftSEO does not follow redirects.
Apps set up by fly launch stop idle Machines and start one when the next request comes in. For a Java, Kotlin or .NET receiver, don’t leave a delivery waiting on that cold boot: add min_machines_running = 1 under [http_service] in fly.toml, or set auto_stop_machines = "suspend".
No card required. Signed test delivery in under a minute.