diff --git a/.github/workflows/vale-docs-linter.yml b/.github/workflows/vale-docs-linter.yml
index 18aab7dfc0..43404c4cf6 100644
--- a/.github/workflows/vale-docs-linter.yml
+++ b/.github/workflows/vale-docs-linter.yml
@@ -14,7 +14,8 @@ jobs:
- name: get changed files
id: changed-files
- uses: tj-actions/changed-files@v44
+
+ uses: tj-actions/changed-files@v46.0.5
with:
fetch_depth: 2
diff --git a/.vale.ini b/.vale.ini
index a0226e3219..29d5c627a5 100644
--- a/.vale.ini
+++ b/.vale.ini
@@ -15,7 +15,7 @@ BasedOnStyles = Fly, Google, proselint, Vale, write-good
Fly.Exclamation = NO
Fly.Headings = NO
Fly.Latin = NO
-# Fly.Machine = NO # we can do an initial search and replace and then this rule will be less noisy and more useful
+Fly.Machine = NO # we can do an initial search and replace and then this rule will be less noisy and more useful
Fly.OxfordComma = NO
Fly.SentenceLength = NO # turn on later; then reduce further?
Fly.Us = NO
@@ -78,4 +78,4 @@ Vale.Spelling = NO # replaced by Fly.Spelling
# Vale.Terms = NO
[formats]
-markerb = md # maps our markerb file extension to md for linting
\ No newline at end of file
+markerb = md # maps our markerb file extension to md for linting
diff --git a/about/billing.html.markerb b/about/billing.html.markerb
index 0c010d067c..9dd7689ac7 100644
--- a/about/billing.html.markerb
+++ b/about/billing.html.markerb
@@ -6,6 +6,8 @@ nav: firecracker
redirect_from: /docs/about/credit-cards/
---
+## Overview
+
Billing for Fly.io is monthly per organization. Organizations are administrative entities that enable you to add members, share app development environments, and manage billing.
New customers and all new organizations (including those created by previously existing customers) are billed monthly for resource usage. Refer to our resource [Pricing page](/docs/about/pricing/) for details.
@@ -20,13 +22,15 @@ To download a past invoice, click **View** for the relevant cycle. You'll be sen
---
-## Legacy Plan billing
+### Understand your invoice charges
-
-Customers who received a $5 free trial usage credit when signing up for the $5/mo Hobby plan will be billed monthly after that credit is used up.
-
+The prices of provisioned services are mostly fixed. If you provision Machines and leave them running all month, we’ll charge you a predictable amount for that Machine.
-Legacy plan billing, and any included usage, is pro-rated over the month. For example, if you signed up for a Scale plan on September 15th, then you'd be charged $14.50 (half of the $29/mo plan cost) right away and you'll have $14.50 of usage included up to September 30th. Then you'll be charged $29 at the start of October and have $29/mo of usage included for that billing cycle.
+A common reason for additional charges is extra RAM usage.
+
+For a breakdown of charges, check out your invoice in the Billing section of the [dashboard](https://fly.io/dashboard/personal/billing).
+
+We’re happy to discuss a refund if you do create compute resources by mistake, or if your app receives unexpected traffic due to an attack and generates a surprisingly large bill. Just send an email to billing@fly.io.
---
@@ -34,7 +38,7 @@ Legacy plan billing, and any included usage, is pro-rated over the month. For ex
Started Machines are billed per second that they're running (the time they spend in the `started` state), based on the price of a named CPU/RAM combination, plus the price of any additional RAM you specify.
-For example, a Machine described in your dashboard as "shared-1x-cpu@1024MB" is the “shared-cpu-1x” Machine size preset, which comes with 256MB RAM, plus additional RAM (1024MB - 256MB = 768MB). For pricing and available CPU/RAM combinations, see [Compute pricing](/docs/about/pricing/#compute).
+For example, a Machine described in your dashboard as "shared-1x-cpu@1024MB" is the “shared-cpu-1x” Machine size preset, which comes with 256MB RAM, plus additional RAM (1024MB - 256MB = 768MB). For pricing and available CPU/RAM combinations, read about [Compute pricing](/docs/about/pricing/#compute).
Stopped and suspended Machines are billed based on their root file system (rootfs) usage per second (the time they spend in the `stopped` or `suspended` state) by $0.15 per GB per month.
@@ -44,7 +48,7 @@ For example, a Machine described in your dashboard as having 1GB of rootfs stopp
## GPU billing
-GPUs are billed per second that the attached Fly Machine is running (the time they spend in the `started` state), based on the per hour cost of the GPU card. See [pricing for GPUs](/docs/about/pricing/#gpus-and-fly-machines).
+GPUs are billed per second that the attached Fly Machine is running (the time they spend in the `started` state), based on the per hour cost of the GPU card. Learn more about [pricing for GPUs](/docs/about/pricing/#gpus-and-fly-machines).
You're also billed for the Fly Machine separately from the GPU.
@@ -54,7 +58,19 @@ You're also billed for the Fly Machine separately from the GPU.
Volume billing is pro-rated to the hour and we subtract the free allowances first for the Launch, Scale, and Enterprise plans (all of which are now discontinued for new customers). For details, see [Volume pricing](/docs/about/pricing/#persistent-storage-volumes).
-If you create a volume, you will be charged for it. You’re billed for volumes that aren’t attached to Machines, and for volumes that are attached to Machines in any state, including stopped Machines.
+If you create a volume, you will be charged for it. You're billed for volumes that aren't attached to Machines, and for volumes that are attached to Machines in any state, including stopped Machines. Volumes in `pending_destroy` state do not accrue charges.
+
+---
+
+## Volume snapshot billing
+
+Volume snapshot billing is pro-rated to the hour and we subtract the free allowance first. For details, see [Volume Snapshots pricing](/docs/about/pricing/#volume-snapshots).
+
+If you create a snapshot of a volume (either manually or through automatic daily snapshots), you will be charged for the storage space it occupies.
+
+Snapshots are stored incrementally, so you're only charged for data that has changed since the previous snapshot for the volume.
+
+For example, if you have a 10GB volume with 2GB of actual data and 5 daily snapshots with minimal changes between them, you might be charged for around 2.5GB of total snapshot storage rather than 50GB (5 × 10GB).
---
@@ -77,18 +93,20 @@ Unified billing allows you to consolidate billing for multiple organizations und
You can either create a new Linked Organization or link an existing one:
-##### New Organization
+#### New Organization
A **Linked Organization** can be created from the [new organization page](/organizations/new). When creating a new organization, you can choose an existing Organization to link it to.
-##### Link an existing Organization
+#### Link an existing Organization
An existing organization can be converted to a **Linked Organization** from the organization's billing page by clicking the "Link billing to another Organization" button.
- Only admins can link an organization to another organization.
- We recommend saving any invoices you may need for account purposes before linking an organization.
-### Some things to consider
+---
+
+## Some things to consider
- Each organization has its own [private network](/docs/networking/private-networking/). Organizations are isolated from each other, which might
be advantageous from a security standpoint.
@@ -97,12 +115,6 @@ be advantageous from a security standpoint.
---
-## Free resource allowances
-
-Fly.io has deprecated plans as of October 7, 2024, but is still honoring them for users who purchased them prior to that date. If your plan includes [free allowances](https://fly.io/docs/about/pricing/#free-allowances), we subtract them off the top of your usage total. Once your usage exceeds the free allowance on any given resource, we start metering usage of that resource for billing purposes.
-
----
-
## Payment options
We process payments through Stripe. Monthly invoicing requires a credit card or credits.
@@ -127,18 +139,6 @@ You can't use a prepaid card as a default (or saved) payment method. You can, ho
---
-## Understand charges beyond the free allowances
-
-The prices of provisioned services are mostly fixed. If you provision Machines and leave them running all month, we’ll charge you a predictable amount for that Machine.
-
-A common reason for additional charges is extra RAM usage.
-
-For a breakdown of charges, check out your invoice in the Billing section of the [dashboard](https://fly.io/dashboard/personal/billing).
-
-We’re happy to discuss a refund if you do create compute resources by mistake, or if your app receives unexpected traffic due to an attack and generates a surprisingly large bill. Just send an email to billing@fly.io.
-
----
-
## Delete your account
To delete your Fly.io account, go to Account > Settings > [Delete Account](https://fly.io/user/deactivate) in your dashboard. You'll see a list of clean-up tasks that need to be done before you can delete the account, including:
@@ -149,3 +149,19 @@ To delete your Fly.io account, go to Account > Settings > [Delete Account](https
- Remove extensions to ensure that you don't get billed by partners when you can no longer access your account.
When you've completed the clean-up tasks, click **Delete** to permanently delete your Fly.io account.
+
+---
+
+## Discontinued/legacy plans
+
+### Legacy Plan billing
+
+
+Customers who received a $5 free trial usage credit when signing up for the $5/mo Hobby plan will be billed monthly after that credit is used up.
+
+
+Legacy plan billing, and any included usage, is pro-rated over the month. For example, if you signed up for a Scale plan on September 15th, then you'd be charged $14.50 (half of the $29/mo plan cost) right away and you'll have $14.50 of usage included up to September 30th. Then you'll be charged $29 at the start of October and have $29/mo of usage included for that billing cycle.
+
+### Free resource allowances for Legacy Plan users
+
+Fly.io has deprecated plans as of October 7, 2024, but is still honoring them for users who purchased them prior to that date. If your plan includes [free allowances](/docs/about/pricing/#legacy-free-allowances), we subtract them off the top of your usage total. Once your usage exceeds the free allowance for any given resource, we start metering usage of that resource for billing purposes.
\ No newline at end of file
diff --git a/about/cost-management.html.md b/about/cost-management.html.md
new file mode 100644
index 0000000000..8b6002655a
--- /dev/null
+++ b/about/cost-management.html.md
@@ -0,0 +1,64 @@
+---
+title: Cost Management on Fly.io
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-10-23
+---
+
+## Predicting your Fly.io bill and avoiding surprises
+
+We’ve done our best to make billing on Fly.io something you can reason about. Machines don’t appear out of nowhere. If something is running, it’s because you launched it, or you configured something that did. In general, when we talk about **autoscaling**, we mean starting or stopping machines you’ve already defined. We don’t quietly spin up extras in the background that turn into mystery charges on your bill. The idea is that your bill should always be traceable to something you can see, name, and plausibly explain when someone asks. If it’s not, we’re happy to help you sort it out.
+
+
+**Need more help understanding your bill?** Reach out to us at [billing@fly.io](mailto:billing@fly.io).
+
+
+## A few examples
+
+So when you’re budgeting, you can look at what you’ve provisioned and do some quick math. Let’s say you’ve deployed three “shared-1x 1GB” machines in `sjc`. That’s $20.37/month, tops. If you let them stop when idle, you might only pay for a few hours of compute—but the worst-case scenario is still capped at that monthly rate.
+
+Here are a few more grounded examples:
+
+- A **staging app** running in the `sjc` region with one `shared-1x 256MB` machine that auto-stops when idle might cost less than **$1/month**. If you leave it running full-time, it’s still only **$2.32/month**.
+- A **queue worker** (also in `sjc`) that uses metrics-based autoscaling on `shared-1x 512MB` machines might average 3–5 machines running during busy hours. If those stay up for 50% of the month, you’re looking at **$5.72–9.52/month**, worst-case around **$19 month** if they all run 24/7.
+
+We recommend budgeting for the “always-on” cost. You can get under that number with auto-stop or bursty usage, but don’t depend on it to hit your budget.
+
+If your estimate seems high, the most predictable way to save money isn’t fiddling with auto-stop/start settings (since any random request might spin a machine up again), but by just…running fewer machines. Or smaller ones.
+
+
+**Want to play with numbers?** Try the [Fly.io pricing calculator](https://fly.io/calculator) to get a rough sense of what your setup might cost. For the full breakdown, here’s our [pricing page](/docs/about/pricing/).
+
+
+## Metrics-based autoscaling can affect costs
+
+There is one important exception to the rule that Fly.io doesn’t create machines on your behalf. If you use the **metrics-based autoscaler**, for example to scale a background worker based on queue depth, it _can_ create or destroy machines automatically.
+
+**When can the metrics-based autoscaler create machines?** When you deploy and configure the [**Fly Autoscaler**](/docs/launch/autoscale-by-metric/), and use `FAS_CREATED_MACHINE_COUNT` instead of `FAS_STARTED_MACHINE_COUNT`, you're giving it permission to create and destroy machines on your behalf. It still uses the machine sizes and regions you specify, and anything it spins up counts toward your bill. [Read more in the docs](/docs/launch/autoscale-by-metric/).
+
+## Other Stuff to Watch For
+
+A few more things that can quietly run up your bill if you're not paying attention:
+
+- **Bandwidth costs can add up.** Outbound data transfer is billed at $0.02/GB in North America and Europe, with higher rates in some other regions. Ingress is free. If you're serving media, syncing large datasets, or replicating across regions, bandwidth can quietly become a big line item. [Learn about data transfer pricing.](/docs/about/pricing/#data-transfer-pricing)
+- **Volumes don’t stop billing when your machines do.** Persistent volumes are billed hourly, whether or not your machine is running. That 3GB volume you forgot about in `fra`? It's still costing you. [Read about storage pricing here](/docs/about/pricing/#volumes).
+- **Volume snapshots will start billing in January 2026.** If you're using automatic volume snapshots as part of your backup strategy, just be aware: starting next year, they’ll be a billable feature. [We’ve announced the change](https://community.fly.io/t/we-are-going-to-start-charging-for-volume-snapshots-from-january-2026/26202) and will share more details well ahead of time.
+- **Managed services live outside your apps.** Fly's [Managed Postgres](/docs/mpg/overview/) (MPG), [Upstash Redis](/docs/upstash/redis/), and [Tigris object storage](/docs/tigris/) don’t get deleted when you delete your apps. You’ll find them in your Dashboard. If you're cleaning up, double-check there too. We've seen people get surprised by bills from leftover services and databases they thought were gone.
+- **Dedicated public IPv4 addresses aren’t free.** Each http app gets billed $2/month if you assign it a dedicated public IPv4. If you’ve got a bunch of apps or deploy previews per branch, this can sneak up on you.
+- **Internal-only apps still count.** Just because a Fly app doesn’t serve web traffic doesn’t mean it’s free. Background workers, queue processors, cron jobs: they all run machines, and those still cost money.
+
+## Understanding Free Usage and Overages
+
+- **Free allowances don’t cap your bill.** We may give you free credits and usage allowances, especially when you’re starting out or have a legacy billing plan. But there’s no soft ceiling. If you go over, we’ll bill you. We don't support billing alerts (yet), so budget accordingly.
+- **There is no "free account/free tier" on Fly.io.** We do have a Free Trial program, which you can read about [here](/docs/about/free-trial/).
+- **Check your dashboard often.** To spot ballooning costs and overages before they become an issue, check the "current month to date bill" item in your dashboard.
+
+## Related Reading
+
+Want to go deeper on scaling strategies, autoscaling configuration, or controlling concurrency? These docs walk through the mechanics:
+
+- [Autoscale Fly Machines](/docs/blueprints/autoscale-machines/) A practical guide to getting basic autoscaling working with Fly Machines. Covers HTTP-based scaling triggers.
+- [Metrics-based Autoscaling](/docs/launch/autoscale-by-metric/) This is the one that _can_ create machines automatically, based on queue depth or another metric you specify. Use it wisely.
+- [Setting Concurrency Limits](/docs/blueprints/setting-concurrency-limits/) Shows how to keep your app from getting overwhelmed by too many concurrent requests. Especially useful if you’re scaling horizontally.
+
diff --git a/about/free-trial.html.md b/about/free-trial.html.md
new file mode 100644
index 0000000000..2c97fa4cf1
--- /dev/null
+++ b/about/free-trial.html.md
@@ -0,0 +1,51 @@
+---
+title: Fly.io Free Trial
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-10-27
+---
+
+**Fly.io runs your apps close to your users. This page explains how our free trial works and what resources you can use before you need to add a payment method.**
+
+## Free Trial overview
+
+A free trial on Fly.io includes 2 hours of machine runtime or 7 days of access, whichever comes first. That’s just enough to get a real app up and running before you decide if it’s a fit for you.
+
+If you hit any of the limits below before your 7 days are up, **your trial is considered exhausted**, and your apps will stop until you add a payment method.
+
+Here’s what’s **included** during the trial:
+
+- **2 total VM hours** (Shared across any machines you launch) **Note: Trial Machines are set to automatically stop after running for 5 minutes**
+- **10 machines max**
+- **20GB of volume storage**
+- Up to **2 vCPUs per machine**
+- **4GB memory per machine**
+
+These are **not included** in the free trial:
+
+- Dedicated IPv4 addresses
+- Access to performance-optimized vCPUs
+- GPU machines
+
+You can add a credit card from the dashboard at any time during the trial. This lifts the resource limits and keeps your apps running without interruption. **Note: adding a card ends the free trial** and your usage starts counting toward your bill from that point on.
+
+## Checking usage
+
+You can see what you’ve used and what’s left under **“Trial Status”** in the Fly.io dashboard. Machines usage (including memory) is tracked per second, while Volumes and Machine count are tracked per hour.
+
+The free trial is meant for kicking the tires. Spin something up, poke around, deploy a real app or two. If Fly.io fits your use case, great! Add a credit card to keep going. If not, no hard feelings.
+
+## What happens when the free trial ends?
+
+If you don’t add a payment method by the end of your 7-day trial, or if you use up the included resources, your apps will stop running. You won’t be able to launch new machines, attach volumes, or deploy changes until billing is set up.
+
+Whether you're testing the waters or gearing up to launch something real, the free trial gives you room to get started. If Fly.io feels like a fit, adding a payment method from the Dashboard is all it takes to keep going. No surprises, no downtime, no pressure.
+
+---
+
+## Related Reading
+
+- [Pricing Overview](/docs/about/pricing/)
+- [How Billing Works](/docs/about/billing/)
+- [Fly.io Pricing Calculator](https://fly.io/calculator)
diff --git a/about/index.html.markerb b/about/index.html.markerb
index 3f97f3b373..e3d8d44840 100644
--- a/about/index.html.markerb
+++ b/about/index.html.markerb
@@ -14,13 +14,19 @@ More details about using and working with the Fly.io platform.
* **[Pricing](/docs/about/pricing/):** Pricing for compute, storage, and all the Fly.io platform services.
-* **[Billing](/docs/about/billing/):** How billing works on Fly.io.
+* **[Billing](/docs/about/billing/):** Learn how billing works on Fly.io.
-* **[Healthcare](/docs/about/healthcare/):** Controls relevant to running healthcare apps on Fly.io.
+* **[Cost Management](/docs/about/cost-management/):** Read this guide to learn how Fly.io billing really works, how to estimate your costs, and what to watch out for, complete with real examples and gotchas.
+
+* **[Free Trial](/docs/about/free-trial/):** Find out about Fly.io's Free Trial.
* **[Support](/docs/about/support):** Who to contact and where to go when you need Fly.io support.
-* **[Extensions program](/docs/about/extensions/) and [Extensions API](/docs/reference/extensions_api/):** Learn about becoming an extensions partner.
+* **[Healthcare](/docs/about/healthcare/):** Controls relevant to running healthcare apps on Fly.io.
+
+* **[Engineering Jobs and Hiring](/docs/about/extensions/):** Learm more about our hiring process.
+
+* **[Open Source](/docs/about/open-source/):** How we help those OSS projects that help us.
* **[Open Source](/docs/about/open-source/):** How we help those OSS projects that help us.
diff --git a/about/merch.html.markerb b/about/merch.html.markerb
new file mode 100644
index 0000000000..858effd3c3
--- /dev/null
+++ b/about/merch.html.markerb
@@ -0,0 +1,28 @@
+---
+title: Fly.io Merchandise
+layout: docs
+nav: firecracker
+---
+
+
+
+
+
+## Fly.io Merchandise
+
+Looking for some superfly swag? We have some Fly merch available in our Redbubble shop. Enjoy!
+
+Visit our [Redbubble shop](https://www.redbubble.com/people/annieruygt/shop) to check out our latest merchandise.
+
+## Available Items
+
+Our Redbubble shop features a variety of Fly.io branded items including:
+- T-shirts
+- Hoodies
+- Stickers
+- Mugs
+- And more!
+
+## Contact
+
+For any questions about our merchandise, please contact us at [annie@fly.io](mailto:annie@fly.io).
diff --git a/about/pricing.html.markerb b/about/pricing.html.markerb
index fb500c95ff..f243365603 100644
--- a/about/pricing.html.markerb
+++ b/about/pricing.html.markerb
@@ -25,39 +25,6 @@ We charge for started and stopped Machines differently. Attached GPUs are charge
### Started Fly Machines
-<% if Time.now.utc < '2024-11-01T00:00:00Z' %>
-
- **Machines pricing is changing!**
-
-
- On July 1st, we're beginning a phased roll-out of per-region pricing. For the first month, prices will remain the same, but the lines on your invoice will be broken down by region. Each subsequent month, prices will be increase by 25% of the difference between the price in iad and the price listed for the region, until the listed price is reached in November.
-
-
- See an example of how the phased roll-out works
-
-
-
- The listed price for shared-1x-256mb Machines in lax is $2.33/month.
- In July, shared-1x-256mb Machines in lax will cost the same as Machines in iad , roughly $1.94/month.
- In August, they'll cost roughly $1.94 + 0.25\*($2.33 - $1.94) = $2.04/month.
- In September, they'll cost roughly $1.94 + 0.5\*($2.33 - $1.94) = $2.14/month.
- In October, they'll cost roughly $1.94 + 0.75\*($2.33 - $1.94) = $2.23/month.
- Finally, in November they'll cost roughly $2.33/month, the listed price for Machines in lax .
-
-
-
-
-
-<% end %>
-
Region:
@@ -135,7 +102,7 @@ For example, if you purchase a $36/year `shared` Machines block in `cdg`, you'll
You can set up reservations via self-service in the billing section of your Fly.io [dashboard](https://fly.io/dashboard). They apply to usage starting on the 1st, so setting up reservations any time in the month will give you the credits the entire month.
-### GPUs and Fly Machines
+### GPU-enabled Fly Machines
Pricing for a GPU-enabled Fly Machine is the price of a standard Fly Machine (see above) plus the price of the attached GPU. Like Machines, GPUs are billed by the second when the attached Machine is running.
@@ -155,8 +122,16 @@ Reserved and dedicated options:
* Discounted rates for reserved GPU Machines and dedicated hosts.
+## Managed Postgres
+
+The price of running Fly.io Managed Postgres depends on your selected Managed Postgres Plan and the amount of storage your Postgres cluster has.
+
+Current pricing for Managed Postgres plans and storage is available [here](/docs/mpg#pricing).
+
## Persistent Storage Volumes
+### Volumes
+
[Fly Volumes](/docs/volumes/) are local persistent storage for Machines.
* $0.15/GB per month of provisioned capacity
@@ -165,6 +140,33 @@ Reserved and dedicated options:
You'll be charged for volumes that you create, whether they are attached to a Machine or not, including when an attached Machine is stopped.
+### Volume Snapshots
+
+<% if Time.now.utc < '2026-03-01T00:00:00Z' %>
+
+ **New charges**
+
+
+ Starting January 1st 2026, we're introducing charges for [volume snapshot](/docs/volumes/snapshots/) storage. You'll see the first charges on the invoice issued at the start of February 2026.
+
+
+
+ If you're an existing customer, you can check your usage in the **Billing** section of the [dashboard](https://fly.io/dashboard/personal/billing) on your Upcoming Invoice and in the Cost Explorer.
+
+
+<% end %>
+
+* $0.08/GB per month
+* First 10GB free each month
+
+[Volume Snapshot billing](/docs/about/billing/#volume-snapshot-billing) is pro-rated to the hour.
+
+Automatic daily snapshots with 5 days retention are enabled by default on new volumes. This can be [adjusted](/docs/volumes/snapshots/#set-or-change-the-snapshot-retention-period) or [disabled](/docs/volumes/snapshots/#disable-automatic-daily-snapshots).
+
+Usage is calculated based on the total stored size of the snapshots, not the provisioned volume size. You're only charged for the actual data stored - if you've written 1GB to a 10GB volume, you'll be charged for around 1GB of snapshot storage.
+
+Snapshots for each volume are stored incrementally, so you'll only be charged for data that has changed since the previously stored snapshot.
+
## Network prices
### Anycast IP addresses
@@ -186,14 +188,13 @@ We bill for data leaving your app destined for the public internet or for apps o
* Data egress to the Internet, from Machine to edge server to Internet
* Data transfer over private network between regions, from Machine to edge server and edge server to Machine
-* Data transfer to some extensions like Upstash Redis (excludes Tigris Object Storage)
+* Data transfer to some extensions like Upstash Redis
The following types of traffic are free:
* All inbound data transfer
* Data transfer between apps or Machines in the same region (for organizations using granular data transfer rates)
* Data transfer from apps without an assigned IP address (for organizations not using granular data transfer rates)
-* Data transfer to Tigris Object Storage
Fly.io pricing is per region group for outbound data transfer. You'll see a more detailed breakdown of cost per region and per traffic type on your monthly invoice.
@@ -207,12 +208,19 @@ Fly.io pricing is per region group for outbound data transfer. You'll see a more
| - Asia Pacific - Oceania - South America | $0.04 per GB | $0.015 per GB |
| - Africa - India | $0.12 per GB | $0.050 per GB |
-To opt-in to granular bandwidth pricing, go to the [**Organizations** page](https://fly.io/organizations) in the dashboard, click the organization name to change, then click **Switch to granular bandwidth pricing**. You won't be able to return back not using the granular data transfer rates once you opt-in.
+To opt-in to granular bandwidth pricing, go to the [**Organizations** page](https://fly.io/organizations) in the dashboard, click the organization name to change, then click **Switch to granular bandwidth pricing**. You won't be able to return to using the non-granular data transfer rates once you opt in.
+
+### Static Egress IPs for Machines
+
+Static egress IPs for Machines provide dedicated outbound IP addresses for your Machines. When you allocate a static egress IP, you'll get both an IPv4 and IPv6 address for this single price.
-### Static Machine IP
+* $0.005 per hour (~$3.60/month)
+* Machines do not have a static IP by default
-Static IPs for Machines are $0.005 per hour.
-Machines do not have a static IP by default.
+## Support
+
+[Community support](https://community.fly.io/) is included for all customers, regardless of usage level.
+You can get access to a support plan by purchasing a Standard ($29/month), Premium ($199/month), or Enterprise (starting at $2500/month) package in the **Support** section of your dashboard. For more about Support, see [Support at Fly.io](/docs/about/support/).
## Fly Kubernetes
@@ -221,12 +229,6 @@ Machines do not have a static IP by default.
* $75/month per cluster
* Plus the cost of [compute](#compute) and [Fly volumes](#persistent-storage-volumes) that you create
-## Fly Postgres
-
-[Fly Postgres](/docs/postgres/) is a PostgreSQL database that you create and then manage yourself. Fly Postgres clusters are Fly Apps that consist of Machines, volumes, and any configured extra memory.
-
-The [Machine price](#compute) and [volume price](#persistent-storage-volumes) for Fly Postgres are the same as any other Machine and volume you'd run on Fly.io. Assuming the Machines are running all the time, the cost for the preset configurations is about $2/month for a single node cluster for dev projects and from about $82 to $164/month for a three-node production cluster. You don't need to keep the preset configurations, you can [scale your Fly Postgres Machines](/docs/postgres/managing/scaling/) to suit your workload at any time.
-
## Extensions
Fly.io offers managed services operated by third parties, such as [Tigris Object Storage](/docs/tigris) and [Upstash Redis](/docs/upstash/redis/).
@@ -239,23 +241,15 @@ You will not be billed separately for:
* IP addresses associated with the service
* Bandwidth to Tigris Object Storage
-You **will** be billed separately for data transfer to services other than Tigris Object Storage. See our [data transfer pricing](#data-transfer-pricing) for details.
-
-## LiteFS Cloud
-
-<%= partial "/docs/partials/docs/litefs_sunset" %>
+You **will** be billed separately for data transfer to these external third-party services. See our [data transfer pricing](#data-transfer-pricing) for details.
-[LiteFS Cloud](/docs/litefs/cloud-backups) is a service that provides streaming
-backups and point-in-time restore for your SQLite-based applications that use
-[LiteFS](/docs/litefs).
+## Unsupported Products
-* $5 per month for up to 10GB of database storage.
-* Additional $0.50/GB per month for database storage above 10GB.
+### Unmanaged Fly Postgres (Unsupported)
-## Support
+[Fly Postgres](/docs/postgres/) is a PostgreSQL database that you create and then manage yourself. Fly Postgres clusters are Fly Apps that consist of Machines, volumes, and any configured extra memory.
-[Community support](https://community.fly.io/) is included for all customers, regardless of usage level.
-You can get access to additional support by purchasing a Standard ($29/month), Premium ($199/month), or Enterprise (starting at $2500/month) package in the **Support** section of your dashboard. For more about Support, see [Support at Fly.io](/docs/about/support/).
+The [Machine price](#compute) and [volume price](#persistent-storage-volumes) for Fly Postgres are the same as any other Machine and volume you'd run on Fly.io. Assuming the Machines are running all the time, the cost for the preset configurations is about $2/month for a single node cluster for dev projects and from about $82 to $164/month for a three-node production cluster. You don't need to keep the preset configurations, you can [scale your Fly Postgres Machines](/docs/postgres/managing/scaling/) to suit your workload at any time.
## Discontinued Plans
@@ -273,14 +267,22 @@ Outbound data transfer:
* 30 GB Asia Pacific, Oceania & South America
* 30 GB Africa & India
-#### Paid Hobby plan and free trial
+### Paid Hobby plan with Free Trial credit
-If you signed up for the now deprecated $5/month Hobby plan before the release of the Pay As You Go plan, you got a one-time $5 free trial credit to let you test-drive Fly.io at no cost. When the free trial credit is used up, we automatically place your organization on the $5/month Hobby plan, which includes $5/month of usage and free allowances. There are no free allowances during the free trial. We'll send you an email when your free trial credit is used up and you won't be charged before that. The free trial credit doesn't expire and applies only to the default (“personal”) organization that we created for you on sign-up.
+If you signed up for the now deprecated $5/month paid Hobby plan prior the release of the Pay As You Go plan, you got a one-time $5 free trial credit to let you test-drive Fly.io at no cost. When the free trial credit was used up, we automatically placed your organization on the $5/month Hobby plan, which included $5/month of usage and free allowances. There are no free allowances during the free trial. We'll send you an email when your free trial credit is used up and you won't be charged before that. The free trial credit doesn't expire and applies only to the default (“personal”) organization that we created for you on sign-up.
To change your plan to the Pay As You Go plan, go to the [**Organizations** page](https://fly.io/organizations) in the dashboard, click the organization name to change, then click **Choose Pay As You Go**. If you change your plan, you won't be able to return to the paid Hobby Plan.
-#### Legacy Hobby plan
+### Legacy (Free) Hobby plan
If you were on the free Hobby plan at the time that the paid Hobby plan became the default for new organizations, your plan is now called the Legacy Hobby plan. Your costs stay the same as they were, with no monthly subscription fee, and no included usage beyond the free resource allowances.
If you change your plan, you won't be able to return to the Legacy Hobby Plan.
+
+## **Related reading**
+
+- [Billing for Fly.io](/docs/about/billing/) How invoicing, payment methods, and usage tracking work.
+- [Cost Management](/docs/about/cost-management/) Best practices for estimating, monitoring, and controlling your spend.
+- [Free Trial](/docs/about/free-trial/) What the Fly.io Free trial gives you and when billing begins.
+- [Organization Roles & Permissions](/docs/security/org-roles-permissions/) How org structure, permissions and billing interplay.
+- [Optimize Compute Costs: Fine‑tune your app](/docs/apps/fine-tune-apps/) Tuning machine size, memory/CPU, and stop‑/start behavior to reduce waste.
\ No newline at end of file
diff --git a/about/support.html.md b/about/support.html.md
index d3a7acab06..1c3b6ab20c 100644
--- a/about/support.html.md
+++ b/about/support.html.md
@@ -2,7 +2,6 @@
title: Support
layout: docs
nav: firecracker
-toc: false
---
@@ -27,9 +26,21 @@ All customers have access to our active [community forum](https://community.fly.
Customers also frequently help each other out and share insights on running their apps on Fly.io. Especially helpful contributors can earn a special "Aeronaut" badge to signify their troubleshooting prowess, great advice, and exceptional kindness.
-## Email support
+## Billing and account support
+
+**Who can use this:** Everyone
-**Who can use this:** Organizations who have purchased a Standard, Premium, or Enterprise Support package or organizations with legacy Launch or Scale plans.
+**Use this for:** Billing and account management issues.
+
+For questions about a specific invoice or account management issues, you can email us at [billing@fly.io](mailto:billing@fly.io).
+
+## Paid support plans
+
+**Find out more:** Want to learn more about our different support plans or ready to get started? Just head over to our [Support page](https://fly.io/support). We've got all the details there to help you choose what works best for you!
+
+### Email support
+
+**Who can use this:** Organizations who have purchased a Standard, Premium, or Enterprise Support package or organizations with legacy Launch or Scale plans.
**Use this for:** Technical support for issues or questions specific to you.
@@ -37,21 +48,19 @@ Our Standard, Premium, and Enterprise packages have access to email support. The
You can find your support address is in the [Fly.io dashboard](https://fly.io/dashboard). Select your organization, and look for your unique email address in the **Support** section. You'll also find a form to submit new support tickets, as well as an overview of recent support interactions.
-## Support Portal
+### Support Portal
-**Who can use this:** Organizations who have purchased a Standard, Premium, or Enterprise Support package or organizations with legacy Launch or Scale plans.
+**Who can use this:** Organizations who have purchased a Standard, Premium, or Enterprise Support package or organizations with legacy Launch or Scale plans. Organizations with a Managed Postgres (MPG) database cluster also have access to our Support Portal for issues related to MPG.
**Use this for:** Technical support for issues or questions specific to you.
The Support Portal is a self-service portal for customers to submit support tickets, view recent support interactions, and track the status of their tickets. You can access it from your [Fly.io dashboard](https://fly.io/dashboard) by clicking the **Support** tab.
-## Billing and account support
+## Managed Postgres Support
-**Who can use this:** Everyone
+We now offer [Managed Postgres (MPG)](https://fly.io/docs/mpg/overview/), a fully managed Postgres service with automated provisioning, daily snapshots, built-in high availability, global networking, and Prometheus-compatible metrics. MPG is currently available in a limited set of regions and supports up to 1 TB of storage, with an initial allocation cap of 500 GB. Managed Postgres customers have Support portal access for issues related to MPG.
-**Use this for:** Billing and account management issues.
-
-For questions about a specific invoice or account management issues, you can email us at [billing@fly.io](mailto:billing@fly.io).
+Note: If you need to run in unsupported regions, require more storage, or need custom configuration, you may still need to use an [unmanaged Postgres](https://fly.io/docs/postgres/getting-started/what-you-should-know/) app for now.
## Support metrics
@@ -79,12 +88,13 @@ Here are some things to include in your ticket:
-
+
Supported Products
**Networking**
**Machines** (including GPUs)
+ **Managed Postgres** (MPG)
**Apps**
**Launch/Deploy** (UI & CLI)
**Volumes**
@@ -92,12 +102,6 @@ Here are some things to include in your ticket:
**Security**
**Accounts & Billing**
**Extensions** (Tigris, Upstash, Depot)
-
-
-
-
-
Limited Support
-
**Monitoring** (metrics and logs)
@@ -112,9 +116,3 @@ Here are some things to include in your ticket:
-
-
-
- Our current Postgres offering is [unmanaged](https://fly.io/docs/postgres/getting-started/what-you-should-know/). In general, [Fly.io](http://fly.io/) provides support for the automated provisioning, daily snapshots, global networking, and Prometheus metrics for Postgres databases, but unfortunately we're not able to provide in-depth guidance or troubleshooting for your unmanaged Postgres database apps.
- We are working on a managed offering, but until then, we recommend that you look at a managed provider such as [Crunchy Bridge](https://www.crunchydata.com/products/crunchy-bridge) if you're running into consistent issues with our unmanaged Postgres.
-
\ No newline at end of file
diff --git a/app-guides/mysql-on-fly.html.markerb b/app-guides/mysql-on-fly.html.markerb
index 6e75f243ce..0769f7b553 100644
--- a/app-guides/mysql-on-fly.html.markerb
+++ b/app-guides/mysql-on-fly.html.markerb
@@ -29,7 +29,7 @@ cd my-mysql
# Run `fly launch` to create an app
# Use the --no-deploy option since we'll make changes before first deploy
# Use the --image option to specify a MySQL Docker image
-fly launch --no-deploy --image mysql:8
+fly launch --no-deploy --image mysql:8.0.37
```
Type `y` when prompted to tweak the default app settings. Then, on the Fly Launch page:
@@ -108,7 +108,7 @@ There are a few important things to note:
1. We added the `[processes]` section for the default `app` process, which lets us pass custom commands (overriding Docker's `CMD`).
* For MySQL 8+, you'll want to use the `mysql_native_password` authentication plugin. As shown in the fly.toml above, there are different syntax for setting this plugin based on the MySQL version.
* If you're using **MySQL 5.7**, your `app` process can be simplified as shown in the `fly.toml` example above, as it uses `mysql_native_password` [by default](https://dev.mysql.com/doc/refman/5.7/en/native-pluggable-authentication.html#:~:text=client%20programs%20use-,mysql_native_password,-by%20default.%20The).
- * **MySQL 8 to 8.3** must explicitly set this with the `--default-authentication-plugin` flag.
+ * **MySQL 8.0.32 to 8.3** must explicitly set this with the `--default-authentication-plugin` flag.
* Starting from **MySQL 8.4**, the `--default-authentication-plugin` option is [deprecated](https://github.com/docker-library/mysql/issues/1048#issuecomment-2088836076). If you're using 8.4+, you can make use of the `--mysql-native-password` flag instead.
@@ -169,7 +169,7 @@ flyctl proxy 13306:3306 -a my-mysql
Then connect to your MySQL server at `localhost:3306` and the username and password credentials from above:
```cmd
-mysql -h localhost -P 3306 -u non_root_user -ppassword some_db
+mysql --protocol=tcp -h localhost -P 3306 -u non_root_user -ppassword some_db
```
## Backups
diff --git a/apps/app-availability.html.markerb b/apps/app-availability.html.markerb
index 2b647bfd48..64a45518b3 100644
--- a/apps/app-availability.html.markerb
+++ b/apps/app-availability.html.markerb
@@ -5,6 +5,10 @@ nav: apps
redirect_from: /docs/reference/app-availability/
---
+
+
+
+
A resilient app is resistant to events like hardware failures or outages. Redundancy, supported by monitoring and scaling, is one of the ways to make your app more resilient, and we provide some built-in features to make this easier.
## What your app needs to do
diff --git a/apps/app-handover-guide.html.md b/apps/app-handover-guide.html.md
new file mode 100644
index 0000000000..26dca973aa
--- /dev/null
+++ b/apps/app-handover-guide.html.md
@@ -0,0 +1,59 @@
+---
+title: App handover guide
+layout: docs
+nav: apps
+author: kcmartin
+date: 2025-07-03
+---
+
+
+**If you're building an app for someone else and they’re going to run it themselves on Fly.io, you’ve got two good options. You can either start development inside their Fly.io organization, or build it in yours and move it over later. Here’s how both paths work, and what can get messy during the move.**
+
+
+---
+
+### Option 1: Build in the customer’s org from day one
+
+The cleanest handoff is no handoff at all. Have the customer create their own Fly.io organization before you start building.
+
+1. **They create the organization and add a payment method.** Basic stuff. This is what Fly.io bills against.
+1. **They invite your devs to their org.** They can do this in the dashboard under “Team → Invite Member.” You now have access to deploy apps directly into their account.
+1. **You build inside their org.** Fly.io treats organizations as isolated namespaces, so there’s no awkward boundary crossing here. It’s just your team shipping code, in their environment.
+1. **They remove you when it’s over.** Or not—you can stick around for maintenance if that’s part of the deal.
+1. **What can get messy.** Make sure that the invited developer is not the only admin user in your customer's org. Double-check that your customer has at least two admins in their org.
+
+---
+
+### Option 2: Build in your org, then move the app to theirs
+
+Sometimes it’s easier to get going inside your own org—especially if you’ve got infra and tooling already set up. When the app is ready, you move it.
+
+Here’s how that works:
+
+1. **Build as usual in your organization.**
+1. **Have the customer invite one of your devs to their org.** This person needs to be in _both_ orgs to do the move.
+1. **Transfer the app**
+1. Use `fly apps move
--org `
+1. Heads up: the move causes a couple minutes of downtime. It’s a good idea to schedule this when traffic is low, and inform your users of a maintenance window.
+1. What moves automatically:
+ - Machines and volumes (data included)
+ - Secrets and environment variables
+ - Certificates and domain names
+ - LiteFS databases (as long as you’re using `$FLY_APP_NAME` for the Consul key)
+1. What doesn’t move automatically:
+ - **Postgres** **(unmanaged and managed):** Moving Postgres databases across orgs is not supported. You’ll need to spin up a new Fly Postgres app in the target org and restore from a volume snapshot. Or you can use `pgloader` to migrate the data.
+ - **Upstash Redis:** This is tied to an org’s private network. You’ll need to provision a new DB in the new org.
+ - **Tigris buckets:** You’ll have to delete the old Tigris bucket, recreate it in the new org, and copy the data over (try `s3sync`). Don’t forget to reset your app’s secrets.
+ - **Sentry and other extensions**: Be sure to create fresh ones in the new org and reconfigure the app to use them.
+
+### Summary
+
+If you know up front that your customer will own the app long-term, starting in their org avoids a bunch of fiddly work later. But if you need to hand over an app from your org to theirs, Fly.io gives you enough tools to pull it off—just be ready to rewire a few things by hand.
+
+
+
+### Related reading
+
+[Fly.io billing](/docs/about/billing)
+
+[Move an app between orgs](/docs/apps/move-app-org)
diff --git a/apps/concurrency.html.markerb b/apps/concurrency.html.markerb
index 163e19402a..bfc57861c0 100644
--- a/apps/concurrency.html.markerb
+++ b/apps/concurrency.html.markerb
@@ -46,3 +46,5 @@ The decision to use `connections` or `requests` for concurrency depends on the t
## Concurrency limit tuning tips
When tuning concurrency, try setting a relatively high `hard_limit`, or leave it unset to have no hard limit. If you do want to set a `hard_limit` to have more control over load balancing, then you might have to do an initial benchmark to estimate the maximum number of concurrent connections or requests that your app can handle. Then tune the `soft_limit` and [create more Machines](/docs/blueprints/resilient-apps-multiple-machines/) to optimize autostop/autostart and load balancing. Once your app is getting real-world traffic, you can continue to monitor your app and adjust the `soft_limit` further to suit your workload.
+
+Want a more detailed guide to concurrency limits? See this blueprint: [Setting Hard and Soft Concurrency Limits on Fly.io](/docs/blueprints/setting-concurrency-limits/).
diff --git a/apps/going-to-production.html.markerb b/apps/going-to-production.html.markerb
index 77fe7e328b..7a173a7cc6 100644
--- a/apps/going-to-production.html.markerb
+++ b/apps/going-to-production.html.markerb
@@ -15,6 +15,10 @@ Use this checklist to help you set up a production environment on Fly.io.
+## Overview
+
+Moving an app from staging to production can expose unexpected failure modes: security holes, performance and scaling issues, or data loss. This checklist is meant to catch common pitfalls for apps on Fly.io, but it’s not a guarantee of production readiness. Not every item here will apply to your app, and you may have additional requirements that aren't listed. Use this as a foundation and adapt it to your needs. Think of this list as a scaffold, not a silver bullet.
+
**Important:** The checklist is not exhaustive and does not guarantee production-readiness for your app. Apps can have unique requirements for production depending on the framework and type of app. Some items won't be applicable and there may be other considerations not listed here; you'll need to decide which checklist items work for your app.
@@ -23,77 +27,78 @@ Use this checklist to help you set up a production environment on Fly.io.
<%= render ChecklistComponent.new(
items: [
- { id: "sso", title: "Set up single sign-on for organizations", description: "Enable SSO on your organization to take advantage of Google or GitHub authentication security. See [Single sign-on for organizations](/docs/security/sso/)." },
- { id: "isolation", title: "Isolate staging and production environments", description: "Use organizations to limit access to your production environment. See [Blueprint: Staging and production isolation](/docs/blueprints/staging-prod-isolation/)." },
- { id: "least-privilege", title: "Enforce least privilege access", description: "Use access tokens to allow only the minimum access required by team members to your organization, apps, and Machines. See [access tokens](https://fly.io/docs/security/tokens/)." },
- { id: "secrets", title: "Protect sensitive information", description: "Set secrets to store sensitive data and make them available as environment variables to your app. See [Secrets and Fly Apps](/docs/apps/secrets/)." },
- { id: "exposure", title: "Make sure private services are not exposed", description: "Check that your private apps with services don't have public IP addresses. Run `fly ips list` and use `fly ips release` to release unnecessary public IPs. See flyctl [`fly ips` commands](/docs/flyctl/ips/). Assign private apps a [Flycast address](https://fly.io/docs/networking/flycast/) instead." },
- { id: "arcjet", title: "Use Arcjet application security for JavaScript apps", description: "Secure your app with rate limiting, bot protection, email validation, and defense against common attacks through our extension partner Arcjet. Currently [free in beta](/docs/security/arcjet/#pricing), but pricing is subject to change. See [Application Security by Arcjet](/docs/security/arcjet/)." }
+ { id: "sso", title: "Set up single sign-on for organizations", description: "Enable SSO on your organization to take advantage of Google or GitHub authentication security. Learn more about [Single sign-on for organizations](/docs/security/sso/)." },
+ { id: "isolation", title: "Isolate staging and production environments", description: "Use organizations to limit access to your production environment. Read this guide: [Staging and production isolation](/docs/blueprints/staging-prod-isolation/)." },
+ { id: "least-privilege", title: "Enforce least privilege access", description: "Use access tokens to allow only the minimum access level required by team members to your organization, apps, and Machines. Understand [access tokens](https://fly.io/docs/security/tokens/)." },
+ { id: "secrets", title: "Protect sensitive information", description: "Set secrets to store sensitive data and make them available as environment variables to your app. Read about [Secrets and Fly Apps](/docs/apps/secrets/)." },
+ { id: "exposure", title: "Make sure private services are not exposed", description: "Check that your private apps with services don't have public IP addresses. Run `fly ips list` and use `fly ips release` to release unnecessary public IPs. More detail is available in this `flyctl` reference: [`fly ips` commands](/docs/flyctl/ips/). Assign private apps a [Flycast address](https://fly.io/docs/networking/flycast/) instead." },
+ { id: "arcjet", title: "Use Arcjet application security for JavaScript apps", description: "Secure your app with rate limiting, bot protection, email validation, and defense against common attacks through our extension partner Arcjet. Read more about [Application Security by Arcjet](/docs/security/arcjet/)." }
],
c: params[:c] || "",
o: params[:o] || "",
h: params[:h] || ""
) %>
-## Networking
+## Databases
<%= render ChecklistComponent.new(
items: [
- { id: "custom-domain", title: "Set up a custom domain", description: "Configure a certificate for your domain. See [Use a custom domain](/docs/networking/custom-domain/)."},
- { id: "ipv4", title: "Consider a dedicated IPv4 address", description: "Completely eliminate the chance of blacklisted spammers causing problems for your app. There is a small [added cost](/docs/about/pricing/#anycast-ip-addresses) for dedicated IPv4 addresses. See [Dedicated IPv4](/docs/networking/services/#dedicated-ipv4)."},
- { id: "flycast", title: "Set up Flycast for private apps", description: "If you haven't already done so, give your private apps a Flycast address to communicate with them entirely on your private network. See [Flycast - Private Fly Proxy services](https://fly.io/docs/networking/flycast/)."}
+ { id: "production-grade-postgres", title: "Use Managed Postgres", description: "We recommend using [Fly.io's Managed Postgres](/docs/mpg/), our fully-managed database service that handles all aspects of running production PostgreSQL."},
+ { id: "test-backups", title: "Practice your disaster recovery plan", description: "Practice restoring your managed Postgres database from a backup before you actually need to. You can do this anytime from the Managed Postgres dashboard." }
],
c: params[:c] || "",
o: params[:o] || "",
h: params[:h] || ""
) %>
-## Databases
+## App performance
<%= render ChecklistComponent.new(
items: [
- { id: "production-grade-postgres", title: "Run \"production-grade\" Postgres", description: "For Fly Postgres, our unmanaged database, set up replication clusters of 3+ servers. See [High Availability & Global Replication](/postgres/advanced-guides/high-availability-and-global-replication/). You can also use an external database provider and configure it for redundancy."},
- { id: "test-backups", title: "Periodically test your Fly Postgres backups", description: "Periodically [create a new Postgres app from a snapshot](/docs/postgres/managing/backup-and-restore/#restoring-from-a-snapshot) or clone the active app, then use `fly postgres connect` to log into the database and confirm that all the data is present." },
- { id: "offsite-backups", title: "Set up offsite backups", description: "For all databases, it's essential to have a recovery plan that includes storing backups offsite. You can use volume snapshots as part of your plan, but you should also store copies of your backups in another location. See [Manage volume snapshots](https://fly.io/docs/volumes/snapshots/) and, for Fly Postgres, [Backup, Restores, & Snapshots](https://fly.io/docs/postgres/managing/backup-and-restore/)." }
+ { id: "machine-sizing", title: "Get Machine sizing right", description: "Most conventional production web apps require [performance CPUs](/docs/machines/cpu-performance/). Also make sure you have enough RAM for your app and/or enable [swapping to disk](/docs/reference/configuration/#swap_size_mb-option) to deal with brief spikes in memory use. Find out more details in our [Machine sizing guide](/docs/machines/guides-examples/machine-sizing/)."},
+ { id: "fine-tune-app", title: "Fine-tune your app", description: "Learn about optimizing your app on Fly.io. Read these tips to [fine-tune your app on Fly.io](/docs/apps/fine-tune-apps/)."}
],
c: params[:c] || "",
o: params[:o] || "",
h: params[:h] || ""
) %>
-## Monitoring
+## Availability, resiliency, and costs
<%= render ChecklistComponent.new(
items: [
- { id: "export-logs", title: "Export your logs", description: "Set up the Fly Log Shipper to aggregate your app’s logs to a service of your choice. See [Export logs](/docs/monitoring/exporting-logs/)."},
- { id: "metrics", title: "Monitor your app with fully-managed metrics", description: "Use managed Prometheus and the Grafana dashboard to monitor your app. See [Metrics on Fly.io](/docs/monitoring/metrics/)."},
- { id: "sentry", title: "Use Sentry for Error tracking", description: "An application monitoring platform that helps you identify and fix software problems before they impact your users from our extension partner Sentry.Fly.io organizations get a year's worth of [Team Plan](https://sentry.io/pricing/+external) credits. See [Application Monitoring by Sentry](/docs/monitoring/sentry/)."}
+ { id: "multiple-machines", title: "Use multiple Machines for resiliency", description: "Make your app resilient to single-host failures with multiple Machines that stay stopped until you need them. Learn more in our guide: [Resilient apps use multiple Machines](/docs/blueprints/resilient-apps-multiple-machines/)."},
+ { id: "add-regions", title: "Scale your app into more regions", description: "Scale your app in multiple regions closest to your app's users. Find out how to [Scale an app's regions](/docs/launch/scale-count/#scale-an-apps-regions)."},
+ { id: "autostop-autostart", title: "Use autostop/autostart to reduce costs", description: "Autostop/autostart lets you stop or suspend Machines when there's low traffic, saving on resource usage and costs. You get autostop/autostart by default with a new app, but you can configure it to optimize for your use case. Find out more: [Autostop/autostart Machines](/docs/launch/autostop-autostart/)."},
+ { id: "autoscale-by-metric", title: "Set up autoscaling by metric to reduce costs", description: "For apps that aren't running web services, use the autoscaler app to scale your app's Machines based on any metric, saving on resource usage and costs. Learn how to [Autoscale based on metrics](/docs/launch/autoscale-by-metric/)."}
],
c: params[:c] || "",
o: params[:o] || "",
h: params[:h] || ""
) %>
-## Availability, resiliency, and costs
+## Networking
<%= render ChecklistComponent.new(
items: [
- { id: "multiple-machines", title: "Use multiple Machines for resiliency", description: "Make your app resilient to single-host failures with multiple Machines that stay stopped until you need them. See [Blueprint: Resilient apps use multiple Machines](/docs/blueprints/resilient-apps-multiple-machines/)."},
- { id: "add-regions", title: "Scale your app into more regions", description: "Scale your app in multiple regions closest to your app's users. See [Scale an app's regions](/docs/launch/scale-count/#scale-an-apps-regions)."},
- { id: "autostop-autostart", title: "Use autostop/autostart to reduce costs", description: "Autostop/autostart lets you stop or suspend Machines when there's low traffic, saving on resource usage and costs. You get autostop/autostart by default with a new app, but you can configure it to optimize for your use case. See [Autostop/autostart Machines](/docs/launch/autostop-autostart/)."},
- { id: "autoscale-by-metric", title: "Set up autoscaling by metric to reduce costs", description: "For apps that aren't running web services, use the autoscaler app to scale your app's Machines based on any metric, saving on resource usage and costs. See [Autoscale based on metrics](/docs/launch/autoscale-by-metric/)."}
+ { id: "custom-domain", title: "Set up a custom domain", description: "Configure a certificate for your domain. Learn how to [use a custom domain](/docs/networking/custom-domain/)."},
+ { id: "ipv4", title: "Consider using a dedicated IPv4 address", description: "Completely eliminate the chance of blacklisted spammers causing problems for your app. There is a small [added cost](/docs/about/pricing/#anycast-ip-addresses) for dedicated IPv4 addresses. Read more about [Dedicated IPv4](/docs/networking/services/#dedicated-ipv4)."},
+ { id: "flycast", title: "Set up Flycast for private apps", description: "If you haven't already done so, give your private apps a Flycast address to communicate with them entirely on your private network. Find out about [Flycast - Private Fly Proxy services](https://fly.io/docs/networking/flycast/)."}
],
c: params[:c] || "",
o: params[:o] || "",
h: params[:h] || ""
) %>
-## App performance
+
+## Monitoring
<%= render ChecklistComponent.new(
items: [
- { id: "machine-sizing", title: "Get Machine sizing right", description: "Most conventional production web apps require performance CPUs. Also make sure you have enough RAM for your app and/or enable [swapping to disk](https://fly.io/docs/reference/configuration/#swap_size_mb-option) to deal with brief spikes in memory use. See [Machine sizing](/docs/machines/guides-examples/machine-sizing/)."},
- { id: "fine-tune-app", title: "Fine-tune your app", description: "Learn about optimizing your app on Fly.io. See [Tips to fine-tune your app on Fly.io](/docs/reference/fine-tune-apps/)."}
+
+ { id: "metrics", title: "Monitor your app with fully-managed metrics", description: "Use managed Prometheus and managed Grafana dashboards to monitor your app. Read about [Metrics on Fly.io](/docs/monitoring/metrics/)."},
+ { id: "sentry", title: "Use Sentry for Error tracking", description: "Our extension partner Sentry provides an application monitoring platform that helps you identify and fix software problems before they impact your users. Fly.io organizations get a year's worth of [Sentry Team Plan](https://fly.io/docs/monitoring/sentry/#sentry-plan-details) credits. Read how to configure [Application Monitoring by Sentry](/docs/monitoring/sentry/)."},
+ { id: "export-logs", title: "Export your logs", description: "Set up the Fly Log Shipper to aggregate your app’s logs to a service of your choice. Read more about [Exporting logs](/docs/monitoring/exporting-logs/)."}
],
c: params[:c] || "",
o: params[:o] || "",
@@ -104,8 +109,8 @@ Use this checklist to help you set up a production environment on Fly.io.
<%= render ChecklistComponent.new(
items: [
- { id: "review-apps", title: "Generate review apps with GitHub Actions", description: "Automatically generate ephemeral review apps on Fly.io for each pull request (PR) using GitHub Actions. See [Blueprint: Git Branch Preview Environments on GitHub](/docs/blueprints/review-apps-guide/)."},
- { id: "deploy-with-github-actions", title: "Deploy with GitHub Actions", description: "Set up your app for continuous deployment to Fly.io from the app’s GitHub repository. See [Continuous Deployment with Fly.io and GitHub Actions](/docs/app-guides/continuous-deployment-with-github-actions/)."}
+ { id: "review-apps", title: "Generate review apps with GitHub Actions", description: "Automatically generate ephemeral review apps on Fly.io for each pull request (PR) using GitHub Actions. Learn more about [Git Branch Preview Environments on GitHub](/docs/blueprints/review-apps-guide/)."},
+ { id: "deploy-with-github-actions", title: "Deploy with GitHub Actions", description: "Set up your app for continuous deployment to Fly.io from the app’s GitHub repository. Find out how to use [Continuous Deployment with Fly.io and GitHub Actions](/docs/app-guides/continuous-deployment-with-github-actions/)."}
],
c: params[:c] || "",
o: params[:o] || "",
@@ -116,8 +121,8 @@ Use this checklist to help you set up a production environment on Fly.io.
<%= render ChecklistComponent.new(
items: [
- { id: "community", title: "Get answers in our community", description: "Check out our [community](https://community.fly.io/) to talk about your project and get help."},
- { id: "email-support", title: "Consider a purchasing email support", description: "Standard or Premium Support packages are available to purchase. See [Support](https://fly.io/support)."}
+ { id: "community", title: "Get answers in our community forum", description: "Check out our [community forum](https://community.fly.io/) to talk about your project and get help."},
+ { id: "email-support", title: "Consider a purchasing a support plan", description: "Standard, Premium, or Enterprise support packages are available to purchase. Learn more about [Support plans](https://fly.io/support)."}
],
c: params[:c] || "",
o: params[:o] || "",
diff --git a/apps/move-app-org.html.markerb b/apps/move-app-org.html.markerb
index df5a962415..0fa8635194 100644
--- a/apps/move-app-org.html.markerb
+++ b/apps/move-app-org.html.markerb
@@ -4,6 +4,10 @@ layout: docs
nav: apps
---
+
+
+
+
You might want to move an app to a new org to hand off your completed work to a client, to take advantage of a new pricing model, or to move a production app out of your personal organization.
You can move an entire app with its resources to another organization with the `fly apps move` command. You'll need to be a member of both organizations to move an app.
@@ -35,5 +39,10 @@ The following app resources are transferred to the new org automatically:
The following extension services need to be reconfigured for the app after the move:
-- **[Upstash for Redis](https://fly.io/docs/upstash/redis/):** Upstash Redis is only available over an organization's private network. After you move the app, you'll need to provision a new database for the new org.
-- **[Tigris object storage](https://fly.io/docs/tigris/):** Before moving the app, you'll need to delete the bucket, then recreate it in the new org and set the new secrets. Billing for the new bucket will be tied to the new org.
+- **[Upstash for Redis](/docs/upstash/redis/):** Upstash Redis is only available over an organization's private network. After you move the app, you'll need to provision a new database for the new org.
+- **[Tigris object storage](/docs/tigris/):** Before moving the app, you'll need to delete the bucket, then recreate it in the new org and set the new secrets. Billing for the new bucket will be tied to the new org.
+
+
+## Read more
+
+[App handover considerations and approaches](/docs/apps/app-handover-guide/)
\ No newline at end of file
diff --git a/apps/secrets.html.markerb b/apps/secrets.html.markerb
index 85278048ca..16a46d2b5c 100644
--- a/apps/secrets.html.markerb
+++ b/apps/secrets.html.markerb
@@ -5,7 +5,9 @@ nav: apps
redirect_from: /docs/reference/secrets/
---
-Specify secrets for your Fly App using the `fly secrets` command. Secrets allow sensitive values, such as credentials, to be passed securely to your Fly App. The secret is encrypted and stored in a vault. An app's secrets are available as environment variables at runtime on every Machine belonging to that Fly App, whether the Machine managed by Fly Launch or not.
+## Overview
+
+You can specify secrets for your Fly App using the `fly secrets` command. Secrets allow sensitive values, such as credentials, to be passed securely to your Fly App. The secret is encrypted and stored in a vault. An app's secrets are available as environment variables at runtime on every Machine belonging to that Fly App, whether the Machine is managed by Fly Launch or not.
Data stored as secrets doesn't have to be sensitive; secrets are made available to the app as environment variables to use for whatever purpose you like.
@@ -13,15 +15,16 @@ If you need secrets to be available when building your Docker image, see [Build
## Architecture
-Secrets are stored in an encrypted vault. When you set a secret through flyctl, it sends the secret value through our API, which writes to the vault for your specific Fly App. The API servers can only encrypt; they cannot decrypt secret values. Secret values are never logged.
+Secrets are stored in an encrypted vault. When you set a secret through `flyctl`, it sends the secret value through our API, which writes to the vault for your specific Fly App. The API servers can only encrypt; they cannot decrypt secret values. Secret values are never logged.
When we launch a Machine for your app, we issue a temporary auth token to the host it runs on. The Fly.io agent on the host uses this token to decrypt your app secrets and inject them into your Machine as environment variables at boot time. When you destroy your Machines, the host environment no longer has access to your app secrets.
-**Warning:** `flyctl` and our API servers are designed to prevent user secrets from being extracted. However, secrets are available to your application code as environment variables. People with deploy access _can_ deploy code that reads secret values and prints them to logs, or writes them to unencrypted data stores.
+**Warning:** `flyctl` and our API servers are designed to prevent user secrets from being extracted. However, secrets are available to your application code as environment variables. People with deploy access **can** deploy code that reads secret values and prints them to logs, or writes them to unencrypted data stores.
-## Set secrets
+## Working with Secrets
+### Set secrets
The `fly secrets set` command sets one or more app secrets, then updates each Machine belonging to that Fly App. This involves a restart of the Machine and a consequent reset of its ephemeral file system.
@@ -37,12 +40,12 @@ fly secrets set DATABASE_URL=postgres://example.com/mydb --stage
In the preceding example, the staged secret will be available only on Machines that are started or updated after the `fly secrets set` command was run.
-The `secrets` command can also take secrets from STDIN. For commands and options, see the [`fly secrets` docs](/docs/flyctl/secrets/) or run `fly secrets --help`.
+The `secrets` command can also take secrets from `stdin`. For commands and options, see the [`fly secrets` docs](/docs/flyctl/secrets/) or run `fly secrets --help`.
**Note:** You can update a machine by triggering a new release with `fly deploy`. Alternatively, the `fly secrets deploy` command will redeploy the current release with the staged secrets. This is helpful if you want to skip rebuilding the image from source code.
-## List secrets
+### List secrets
List secrets that are set for your app. The list shows only the secret name; the value is not shown as it is a secret.
@@ -60,7 +63,7 @@ fly secrets list
For security reasons, we do not allow read access to the plain-text values of secrets.
-## Remove secrets
+### Remove secrets
Remove one or more secret values from your app by name.
@@ -69,3 +72,29 @@ The following example removes two secrets from the app:
```cmd
fly secrets unset MY_SECRET DATABASE_URL
```
+
+### Mounting Secrets as Files
+
+You can write a secret’s value directly to the machine's filesystem at startup using the `[[files]]` section in `fly.toml`. This is useful for things like private keys your app expects on disk.
+
+Secrets must be base64‑encoded.
+
+Example:
+
+```cmd
+fly secrets set SUPER_SECRET=$(cat filename.txt | base64)
+```
+
+`secret_name`: The name of an app secret that’s been set with `fly secrets set`. The value of the secret will be written to the file. The referenced secret’s value must be base64 encoded.
+
+`guest_path`: The full path inside the Machine where the file will be written at boot.
+
+```toml
+[[files]]
+ guest_path = "/path/to/secret.txt"
+ secret_name = "SUPER_SECRET"
+```
+
+You can use this with other `[[files]]` entries, check the `fly.toml` [reference](/docs/reference/configuration/#the-files-section) for more details.
+
+
diff --git a/blueprints/autoscale-machines.html.md b/blueprints/autoscale-machines.html.md
index dc96ede511..628ad55bcf 100644
--- a/blueprints/autoscale-machines.html.md
+++ b/blueprints/autoscale-machines.html.md
@@ -1,16 +1,22 @@
---
title: Autoscale Machines
layout: docs
-nav: firecracker
+nav: guides
redirect_from: /docs/blueprints/autoscale-machines-like-a-boss/
---
+
+
+
+
+## Overview
+
You have an app with services that's configured to [automatically start
-and stop Machines based on traffic demand]((/docs/launch/autostop-autostart/)). But the traffic to your app changes
+and stop Machines based on traffic demand](/docs/launch/autostop-autostart/). But the traffic to your app changes
significantly during the day and you don't want to keep a lot of stopped
Machines during the period of low traffic.
-This blueprint will guide you through the process of configuring the
+This guide will take you through the process of configuring the
[`fly-autoscaler` app](/docs/launch/autoscale-by-metric/) in conjunction with
[Fly Proxy autostop/autostart](/docs/launch/autostop-autostart/) to
always keep a fixed number of Machines ready to be quickly started
@@ -59,14 +65,14 @@ target app up and down:
```
$ fly tokens create deploy -a my-target-app
-$ fly secrets set -o my-autoscaler --stage FAS_API_TOKEN="FlyV1 ..."
+$ fly secrets set -a my-autoscaler --stage FAS_API_TOKEN="FlyV1 ..."
```
Create a read-only token so that the autoscaler app has access to a Prometheus instance:
```
-$ fly tokens create readonly -o my-org
-$ fly secrets set -o my-autoscaler --stage FAS_PROMETHEUS_TOKEN="FlyV1 ..."
+$ fly tokens create readonly
+$ fly secrets set -a my-autoscaler --stage FAS_PROMETHEUS_TOKEN="FlyV1 ..."
```
Configure your autoscaler `fly.toml` like this:
@@ -104,10 +110,8 @@ And finally, deploy the autoscaler, using the `--ha` option to deploy only one M
$ fly deploy --ha=false
```
-## Read more
+## Related reading
- [Autoscale based on metrics](/docs/launch/autoscale-by-metric/)
-
- [Autostop/autostart Machines](/docs/launch/autostop-autostart/)
-
- [Autostop/autostart private apps](/docs/blueprints/autostart-internal-apps/)
diff --git a/blueprints/autostart-internal-apps.html.md b/blueprints/autostart-internal-apps.html.md
index f55443af67..9f9c9c96e4 100644
--- a/blueprints/autostart-internal-apps.html.md
+++ b/blueprints/autostart-internal-apps.html.md
@@ -1,14 +1,20 @@
---
title: Autostart and autostop private apps
layout: docs
-nav: firecracker
+nav: guides
---
+
+
+
+
+## Overview
+
You have a private, or internal, app that communicates only with other apps on your [private network](/docs/networking/private-networking/). This private app might be a database, authentication server, or any other "backend" app that you don't want exposed to the public Internet. You want the app's Machines to stop when they're not serving requests from your other apps, and start again automatically when needed.
To use Fly Proxy autostop/autostart you need to configure services in `fly.toml`, like you would for a public app. But instead of using a public Anycast address, you assign a Flycast address to expose those services only on your private network.
-This blueprint focuses on using autostop/autostart to control Machines based on incoming requests. But when you use Flycast for private apps you also get other [Fly Proxy features](/docs/reference/fly-proxy/) like geographically aware load balancing.
+This guide focuses on using autostop/autostart to control Machines based on incoming requests. But when you use Flycast for private apps you also get other [Fly Proxy features](/docs/reference/fly-proxy/) like geographically aware load balancing.
Learn more about [Flycast](/docs/networking/flycast/).
@@ -109,10 +115,9 @@ Run `fly deploy` for the configuration changes to take effect.
Other apps in your organization can now reach your private app using the [Flycast](/docs/networking/flycast/) IP address or the `.flycast` domain.
-## Read more
+## Related reading
We've talked about Flycast in some past blog posts:
- [Deploy Your Own (Not) Midjourney Bot on Fly GPUs](https://fly.io/blog/not-midjourney-bot/)
-
- [Scaling Large Language Models to zero with Ollama](https://fly.io/blog/scaling-llm-ollama/)
diff --git a/blueprints/bridge-deployments-wireguard.html.md b/blueprints/bridge-deployments-wireguard.html.md
index eb6c0f9804..e2dace868a 100644
--- a/blueprints/bridge-deployments-wireguard.html.md
+++ b/blueprints/bridge-deployments-wireguard.html.md
@@ -2,14 +2,20 @@
title: Bridge your other deployments to Fly.io
layout: docs
sitemap: true
-nav: firecracker
+nav: guides
author: xe
categories:
- networking
date: 2024-05-27
---
-Sometimes you can recreate production on Fly.io without any issues. Other times you need to be able to incrementally move things over. Fly.io [has private networking](https://fly.io/docs/networking/private-networking/) by default for apps in the same organization, but you can also easily connect your existing external servers to this private network. This lets you use your private network as a way to incrementally move services over.
+
+
+
+
+## Overview
+
+Sometimes you can recreate production on Fly.io without any issues. Other times you need to be able to incrementally move things over. Fly.io [has private networking](/docs/networking/private-networking/) by default for apps in the same organization, but you can also easily connect your existing external servers to this private network. This lets you use your private network as a way to incrementally move services over.
Say you have an existing service on AWS or a database with RDS that needs to be accessed over RDS. You can use that AWS instance to pivot traffic from your Fly Machines to those other services. You can also go the other way and access your Fly apps (such as [an instance of Ollama](https://fly.io/blog/scaling-llm-ollama/)) from AWS, your laptop, or any other self-hosted server you have in your arsenal. Or maybe you're making a tool for a support team, and it needs to hit that one database on prem.
@@ -28,7 +34,7 @@ Once that's done, you're off to the races!
On your laptop, make the config for the target server with `fly wireguard create`. You'll need the following information:
- The organization you want to join that server to (such as `personal`, or your company's name).
-- The region you want to create the peer in: you should choose [the closest region to the target computer](https://fly.io/docs/reference/regions/).
+- The region you want to create the peer in: you should choose [the closest region to the target computer](/docs/reference/regions/).
- If you're really not sure which region is the closest, here's a magic command you can try: `curl -Iso /dev/null -w '%header{fly-request-id}' https://fly.io | cut -d- -f2`.
- The name you want to use for that computer on your Fly network, such as the hostname.
- A path to put the generated WireGuard config, such as `~/fly0.conf` to drop it as `fly0.conf` in your home directory.
@@ -105,3 +111,9 @@ Getting ping working should be good enough to get you started, but here's some o
- Connect a few other computers to your private network. Install a Minecraft server on one of them and play together.
Hint: you may need to allow private network addresses through your firewall. Check the documentation of your firewall tool of choice and allow traffic from `fly0` through as if it's an "internal" interface.
+
+## Related Reading
+
+- [Jack into your private network with WireGuard](https://fly.io/docs/blueprints/connect-private-network-wireguard/) A guide showing how to connect a laptop or external server to your Fly.io private network via WireGuard.
+- [Private Networking](https://fly.io/docs/networking/private-networking/) Overview of Fly’s internal IPv6 mesh (6PN), how apps talk to each other privately, and how WireGuard‑based tunnels integrate.
+- [Custom Private Networks](https://fly.io/docs/networking/custom-private-networks/) How to isolate tenants, apps, or services by creating separate private networks inside your organization — relevant when bridging external infrastructure.
\ No newline at end of file
diff --git a/blueprints/cell-based.html.md b/blueprints/cell-based.html.md
index a367dffada..7700190cf6 100644
--- a/blueprints/cell-based.html.md
+++ b/blueprints/cell-based.html.md
@@ -1,7 +1,7 @@
---
title: Cell-based architecture
layout: docs
-nav: firecracker
+nav: guides
author: rubys
categories:
- shared-nothing
diff --git a/blueprints/connect-private-network-wireguard/index.html.md b/blueprints/connect-private-network-wireguard/index.html.md
index e65e95d39e..fa33cdd38d 100644
--- a/blueprints/connect-private-network-wireguard/index.html.md
+++ b/blueprints/connect-private-network-wireguard/index.html.md
@@ -2,7 +2,7 @@
title: Jack into your private network with WireGuard
layout: docs
sitemap: true
-nav: firecracker
+nav: guides
author: xe
categories:
- networking
diff --git a/blueprints/connecting-to-user-machines-http-flow.svg b/blueprints/connecting-to-user-machines-http-flow.svg
new file mode 100644
index 0000000000..148bd9de69
--- /dev/null
+++ b/blueprints/connecting-to-user-machines-http-flow.svg
@@ -0,0 +1 @@
+Router App: llm-router.fly.dev:443
Management Gateway: llm-gateway.flycast:9090
\ No newline at end of file
diff --git a/blueprints/connecting-to-user-machines.html.md b/blueprints/connecting-to-user-machines.html.md
new file mode 100644
index 0000000000..290a74cdb6
--- /dev/null
+++ b/blueprints/connecting-to-user-machines.html.md
@@ -0,0 +1,164 @@
+---
+title: Connecting to User Machines
+layout: docs
+nav: guides
+date: 2025-04-02
+---
+
+
+
+
+
+## Overview
+
+When running machines for end users, a common challenge is efficiently managing and routing requests to these machines. This guide outlines the recommended approach for connecting to user machines on Fly.io.
+
+## Typical Stack
+
+- **Coordinator**: Your app that manages machines on behalf of end users. This can also function as the Router.
+- **Router**: An app that relays requests to user machines. This can be the same as the Coordinator.
+- **User App**: Apps assigned to specific users on isolated networks.
+- **User Machine**: Machine(s) in those apps.
+
+## HTTP Services
+
+A typical setup includes two services:
+- A **management** HTTP service (`port 9090`) for tasks such as:
+ - Sending LLM generated code changes
+ - Health checks
+ - Metrics requests
+- A **public** HTTP service (`port 4443`) for:
+ - Live Previews
+ - LLM <-> MCP Server Connectivity
+ - Deployed User Apps
+
+### HTTP Request Processing
+
+
+
+This diagram illustrates how HTTP requests are processed:
+- Public HTTP requests arrive at your router app, which issues a fly-replay to a specific user app. The Fly Proxy then replays the request to the target app, matching the service based on the inbound request port.
+- Private HTTP requests arrive at an internal management gateway, which is not accessible from the internet. The same replay process gets that request to the user app on the specified port, matching the service based on the inbound request port.
+
+## Using fly-replay
+
+The recommended mechanism for connecting to user machines over HTTP is the `fly-replay` response header. This works by instructing our global proxy to reroute the request to a specific machine. If the machine is not running, the proxy will start it. This approach works for any kind of HTTP request, including WebSockets. Since our proxy manages the entire request, your router app is no longer a point of failure after it sends the `fly-replay` header.
+
+### Port Matching
+
+When using fly-replay, the proxy matches the incoming request's port to the target service's public port. For example:
+- A request to port 80 will be routed to a service configured with `port: 80`
+- A request to port 9090 will be routed to a service configured with `port: 9090`
+
+This means your router app needs to receive requests on the same ports that your target services are configured to use. For example, to connect to a management API on port 9090, the router must receive the request on port 9090.
+
+### Private Network Router Example
+
+The [private-network-router](https://github.com/fly-apps/private-network-router) example shows how to build a router that can handle multiple services. It uses flycast to keep management APIs private while still allowing routing to them. The router:
+
+1. Receives requests on any port
+2. Determines the target app and machine based on the request
+3. Sends a `fly-replay` response header with parameters like `app` and `instance` to route the request to the correct app and machine. For more details on the parameters, see the [fly-replay documentation](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header).
+
+This pattern allows you to:
+- Keep management APIs private by running them with flycast
+- Route requests to multiple services from a single router
+- Maintain security by not exposing management ports directly to the internet
+
+## Authentication
+
+Validation of replayed requests should happen within the user machine. We recommend using the `state` mechanism in fly-replay for this purpose:
+
+1. **Create a preshared key for each user app** and set it as an app secret.
+2. When issuing a replay, set the header: `fly-replay: app=,state=`.
+3. On the user machine, parse the `fly-replay-src` header. This header contains fields such as `instance`, `region`, `t`, and `state` (see [fly-replay documentation](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header)).
+4. Check the `state` value in `fly-replay-src` against the preshared key for the app. If it matches, the request is validated.
+
+See the [TypeScript example below](#example-authenticating-a-replayed-request-in-typescript) for a practical implementation of this validation.
+
+## Antipatterns
+
+- **flycast, .fly.dev, and Dedicated IPs**: These are meant for permanent apps that need to communicate with each other. They and involve more moving parts than simply routing with `fly-replay`. More moving parts mean more potential for issues.
+- **TCP or UDP**: Avoid using raw TCP or UDP when communicating with end user machines. If necessary, consider running Tailscale containers alongside your code.
+
+## Example Service Configuration
+
+Here's an example service configuration for a machine that includes both a management service and a standard public HTTP service:
+
+```json
+{
+ "services": [
+ {
+ "name": "management",
+ "internal_port": 9090,
+ "ports": [
+ {
+ "port": 9090,
+ "handlers": ["http"]
+ }
+ ]
+ },
+ {
+ "name": "public",
+ "internal_port": 8080,
+ "ports": [
+ {
+ "port": 80,
+ "handlers": ["http"]
+ }
+ ]
+ }
+ ]
+}
+```
+
+This configuration sets up:
+- A management service listening internally on port 9090 and exposed on port 9090
+- A public service listening internally on port 8080 and exposed on port 80
+Both services will automatically start when needed and stop when idle.
+
+### Example: Authenticating a Replayed Request in TypeScript
+
+Here's a simple example of how you might authenticate a replayed request in a TypeScript HTTP handler using the `fly-replay-src` header and a preshared key:
+
+```typescript
+// Example preshared key for the app (in practice, load from env or secret store)
+const PRESHARED_KEY = process.env.PRESHARED_KEY;
+
+function parseFlyReplaySrc(header: string | undefined) {
+ if (!header) return {};
+ return Object.fromEntries(
+ header.split(',').map(pair => {
+ const [key, value] = pair.split('=');
+ return [key.trim(), value?.trim()];
+ })
+ );
+}
+
+async function authenticateReplay(request: Request): Promise {
+ const flyReplaySrc = request.headers.get('fly-replay-src');
+ const params = parseFlyReplaySrc(flyReplaySrc);
+ return params.state === PRESHARED_KEY;
+}
+
+// Example usage in a fetch handler
+addEventListener('fetch', (event) => {
+ event.respondWith(handleRequest(event.request));
+});
+
+async function handleRequest(request: Request): Promise {
+ if (!(await authenticateReplay(request))) {
+ return new Response('Unauthorized', { status: 401 });
+ }
+ // ...handle the authenticated request...
+ return new Response('OK');
+}
+```
+
+This code checks the `fly-replay-src` header, parses out the `state` value, and compares it to the preshared key. If it matches, the request is authenticated.
+
+## Related reading
+
+- [Per‑User Dev Environments with Fly Machines](/docs/blueprints/per-user-dev-environments/) Find out how to run isolated, on‑demand environments for each user.
+- [Observability for User Apps](/docs/blueprints/observability-for-user-apps/) Learn about how to gather logs and telemetry from per‑user apps/machines once you’ve routed traffic to them.
+- [Dynamic Request Routing with fly‑replay](/docs/networking/dynamic-request-routing/) Learn about the `fly‑replay` header and dynamic request routing.
\ No newline at end of file
diff --git a/blueprints/connecting-to-user-machines.mmd b/blueprints/connecting-to-user-machines.mmd
new file mode 100644
index 0000000000..140b97d23c
--- /dev/null
+++ b/blueprints/connecting-to-user-machines.mmd
@@ -0,0 +1,10 @@
+graph TD;
+ A[Public HTTP Request] -->|Arrives at| B[Router App: llm-router.fly.dev:443]
+ B -->|Sets fly-replay| C[Fly Proxy]
+ C -->|replay| D[User App: llm-user-1234]
+ D -->|match port 443| E[public:443]
+
+ F[Private HTTP Request] -->|Arrives at| G[Management Gateway: llm-gateway.flycast:9090]
+ G -->|Sets fly-replay| C
+ C -->|replay| D
+ D -->|match port 9090| H[management:9090]
\ No newline at end of file
diff --git a/blueprints/deno-kv-litefs.html.markerb b/blueprints/deno-kv-litefs.html.markerb
index d6f7b375e9..59ae1c52d2 100644
--- a/blueprints/deno-kv-litefs.html.markerb
+++ b/blueprints/deno-kv-litefs.html.markerb
@@ -1,6 +1,7 @@
---
title: Deno KV with LiteFS Cloud
layout: docs
+published: false
nav: firecracker
author: jesse
date: 2024-05-28
diff --git a/blueprints/going-to-production-with-healthcare-apps.html.md b/blueprints/going-to-production-with-healthcare-apps.html.md
index a006609ec6..aa78b1096d 100644
--- a/blueprints/going-to-production-with-healthcare-apps.html.md
+++ b/blueprints/going-to-production-with-healthcare-apps.html.md
@@ -1,13 +1,19 @@
---
title: Going to Production with Healthcare Apps
layout: docs
-nav: firecracker
+nav: guides
redirect_from: /docs/blueprints/going-to-production-with-hipaa-apps
---
+
+
+
+
+## Overview
+
Fly.io was built by security researchers from the ground up to be both productive and secure, making it a great home for HIPAA-compliant production healthcare applications for productive teams that ship often.
-This blueprint runs a developer or operations engineer through the process of evaluating Fly.io's security for HIPAA healthcare apps, launching a pilot application, signing a Business Associate Agreement (BAA), and deploying to production.
+This guiide runs a developer or operations engineer through the process of evaluating Fly.io's security for HIPAA healthcare apps, launching a pilot application, signing a Business Associate Agreement (BAA), and deploying to production.
## HIPAA and Fly.io Primer
@@ -20,7 +26,7 @@ Fly.io takes a "principle of least privilege" approach to security. Here are the
HIPAA requires that PHI be encrypted both in transit and at rest to prevent unauthorized access:
- **In Transit**: Fly.io uses [WireGuard](https://fly.io/blog/our-user-mode-wireguard-year/) to encrypt data as it moves between networks, ensuring compliance with HIPAA's transmission security requirements.
-- **At Rest**: Fly.io ensures data at rest is secured on [encrypted NVMe user volumes](https://fly.io/docs/volumes/), aligning with the encryption standards required by HIPAA for storage security.
+- **At Rest**: Fly.io ensures data at rest is secured on [encrypted NVMe user volumes](/docs/volumes/), aligning with the encryption standards required by HIPAA for storage security.
### Access Control
@@ -42,7 +48,7 @@ HIPAA demands that you implement hardware, software, and procedural mechanisms t
To comply with HIPAA, entities must ensure that PHI is not altered or destroyed in an unauthorized manner:
-- **Data Storage and Backup**: Fly.io’s infrastructure provides robust data integrity controls, including [regular snapshots](https://fly.io/docs/volumes/volume-manage/) of [encrypted volumes](https://fly.io/docs/volumes/overview/#volume-encryption) to prevent data loss.
+- **Data Storage and Backup**: Fly.io’s infrastructure provides robust data integrity controls, including [regular snapshots](/docs/volumes/volume-manage/) of [encrypted volumes](/docs/volumes/overview/#volume-encryption) to prevent data loss.
- **Reproducible Deployments**: Fly.io's use of Docker ensures that application deployments are reproducible and can be locked down to a mostly read-only state. This reduces the likelihood of unauthorized modifications to application environments, meeting HIPAA's requirements for protecting PHI from unauthorized alteration or destruction.
### Physical Security
@@ -68,19 +74,16 @@ If the default region is not where you want to host your application, run `fly p
```bash
fly platform regions
-NAME CODE GATEWAY LAUNCH PLAN + ONLY GPUS
-Amsterdam, Netherlands ams ✓ ✓
-Ashburn, Virginia (US) iad ✓ ✓
-Atlanta, Georgia (US) atl
-Bogotá, Colombia bog
-Boston, Massachusetts (US) bos
-Bucharest, Romania otp
-Chicago, Illinois (US) ord ✓
-Dallas, Texas (US) dfw ✓
-Denver, Colorado (US) den
-Ezeiza, Argentina eze
-Frankfurt, Germany fra ✓ ✓
-Guadalajara, Mexico gdl
+NAME CODE GATEWAY GPUS CAPACITY LAUNCH PLAN+
+
+North America
+Ashburn, Virginia (US) iad ✓ ✓ 164
+Chicago, Illinois (US) ord ✓ 385
+Dallas, Texas (US) dfw ✓ 426
+Los Angeles, California (US) lax ✓ 635
+San Jose, California (US) sjc ✓ ✓ 2399
+Secaucus, NJ (US) ewr 233
+Toronto, Canada yyz ✓ 70
# ... Lots more regions...
```
@@ -177,3 +180,8 @@ Finally run `fly deploy --path fly.production.yml` to deploy your application, t
## Wrap-up
Fly.io is a secure, productive platform for deploying HIPAA-compliant healthcare applications. The platform provides the necessary security controls and features to ensure that PHI is protected in accordance with HIPAA requirements while giving development teams the flexibility to ship often and scale quickly.
+
+## Related reading
+
+- [Healthcare apps on Fly.io](/docs/about/healthcare/) Overview of how Fly.io supports HIPAA‑compliant apps, including encryption, network isolation, and audit‑controls.
+- [Going to production checklist](/docs/apps/going-to-production/) A general production‑readiness checklist for apps on Fly.io.
\ No newline at end of file
diff --git a/blueprints/index.html.erb b/blueprints/index.html.erb
deleted file mode 100644
index f7aa7a3fa1..0000000000
--- a/blueprints/index.html.erb
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Blueprints
-layout: docs
-toc: false
-nav: firecracker
----
-
-
-
-
-
-
- A new—and growing!—library of “blueprints” showing how to run, design, build, and deploy different kinds of apps on Fly.io. This isn’t just a showcase for stuff we’ve built, but a collection of patterns and examples that you can can apply in your own projects.
-
-
-
- <% current_page.children.each do |page| %>
- <% next if page.data['published'] == false %>
-
- <%= link_to_page page %>
-
- <% end %>
-
\ No newline at end of file
diff --git a/blueprints/index.html.md b/blueprints/index.html.md
new file mode 100644
index 0000000000..02af045ab7
--- /dev/null
+++ b/blueprints/index.html.md
@@ -0,0 +1,68 @@
+---
+title: Guides (Blueprints)
+layout: docs
+toc: true
+nav: guides
+---
+
+A growing library of guides for running, designing, and deploying all kinds of apps on Fly.io. Whether you're just getting started or looking for new ideas, these are real-world patterns you can learn from and build on.
+
+## Architecture Patterns
+
+Guides for the structure your app on Fly.io. Layouts, tradeoffs, moving parts.
+
+- [Resilient apps use multiple Machines](/docs/blueprints/resilient-apps-multiple-machines/)
+- [Getting Started with N-Tier Architecture](/docs/blueprints/n-tier-architecture/)
+- [Shared Nothing Architecture](/docs/blueprints/shared-nothing/)
+- [Session Affinity (a.k.a. Sticky Sessions)](/docs/blueprints/sticky-sessions/)
+- [Multi-region databases and fly-replay](/docs/blueprints/multi-region-fly-replay/)
+- [Deploying Remote MCP Servers](/docs/blueprints/remote-mcp-servers/)
+
+
+## Deployment & Developer Workflow
+
+Stuff you set up once and adjust when you ship code. Includes previews, base images, staging, and Docker wrangling.
+
+- [Seamless Deployments on Fly.io](/docs/blueprints/seamless-deployments/)
+- [Rollback Guide](/docs/blueprints/rollback-guide/)
+- [Git Branch Preview Environments on Github](/docs/blueprints/review-apps-guide/)
+- [Staging and production isolation](/docs/blueprints/staging-prod-isolation/)
+- [Per-User Dev Environments with Fly Machines](/docs/blueprints/per-user-dev-environments/)
+- [Using base images for faster deployments](/docs/blueprints/using-base-images-for-faster-deployments/)
+- [Managing Docker Images with Fly.io's Private Registry](/docs/blueprints/using-the-fly-docker-registry/)
+
+
+## Networking & Connectivity
+
+Connecting things. Public apps, private services, bridging deployments, SSH, and WireGuard.
+
+- [Run private apps with Flycast](/docs/blueprints/private-applications-flycast/)
+- [Jack into your private network with WireGuard](/docs/blueprints/connect-private-network-wireguard/)
+- [Bridge your other deployments to Fly.io](/docs/blueprints/bridge-deployments-wireguard/)
+- [Connecting to User Machines](/docs/blueprints/connecting-to-user-machines/)
+- [Run an SSH server](/docs/blueprints/opensshd/)
+
+## Scaling, Performance & Observability
+
+Make it fast. Make it reliable. Monitor what's happening.
+
+- [Observability for User Apps](/docs/blueprints/observability-for-user-apps/)
+- [Autoscale Machines](/docs/blueprints/autoscale-machines/)
+- [Autostart and autostop private apps](/docs/blueprints/autostart-internal-apps/)
+- [Setting Hard and Soft Concurrency Limits](/docs/blueprints/setting-concurrency-limits/)
+- [Using Fly Volume forks for faster startup times](/docs/blueprints/volume-forking/)
+- [Going to Production with Healthcare Apps](/docs/blueprints/going-to-production-with-healthcare-apps/)
+
+
+## Background Jobs & Automation
+
+How to run periodic tasks, long-running jobs, infrastructure automation, and the things that run when you’re asleep.
+
+- [Building Infrastructure Automation without Terraform](/docs/blueprints/infra-automation-without-terraform/)
+- [Deferring long-running tasks to a distributed work queue](/docs/blueprints/work-queues/)
+- [Task scheduling guide with Cron Manager and friends](/docs/blueprints/task-scheduling/)
+- [Crontab with Supercronic](/docs/blueprints/supercronic/)
+
+
+
+
diff --git a/blueprints/infra-automation-without-terraform.html.md b/blueprints/infra-automation-without-terraform.html.md
new file mode 100644
index 0000000000..0625b42aac
--- /dev/null
+++ b/blueprints/infra-automation-without-terraform.html.md
@@ -0,0 +1,173 @@
+---
+title: Building Infrastructure Automation without Terraform
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-08-05
+---
+
+
+
+
+
+## Overview
+
+We don’t have a Terraform provider anymore, and that’s intentional. It’s not a great fit for how our platform works. This guide is about how to get a Terraform-like experience using the tools that do fit.
+
+If you're used to defining infrastructure declaratively, using templated variables, repeatable workflows, a single source of truth, you can absolutely have that on Fly.io. But you’ll get there by thinking a little differently: using tools built around our actual primitives instead of trying to bend them to fit a plan/apply model. If you're looking for a good path forward, you've got options.
+
+This guide walks through two options for automating Fly.io deployments without Terraform.
+
+The first uses `flyctl` and GitHub Actions. It’s what we recommend for most users: push to `main`, your app gets deployed. No infra state to manage, no VMs to track. Just code and Git.
+
+The second uses the Machines API directly. It’s more work, but gives you full control over each virtual machine. You’ll need this if you’re doing custom orchestration like rolling deploys across regions, temporary environments, or using fine-tuned scaling logic.
+
+We’ll show you how both approaches work and help you figure out which one makes sense for your setup.
+
+---
+
+## The easy way: `flyctl` + GitHub Actions
+
+This is the path most Fly.io apps take, and it’s what we recommend. You write code, you push to `main`, your app gets deployed. It’s quick to set up, and it abstracts away the VM lifecycle stuff unless and until you want it.
+
+Here’s the gist:
+
+1. Get your app running on Fly.io
+1. Push the code to a GitHub repo
+1. Wire up a GitHub Actions workflow that calls `flyctl deploy`
+
+That’s it. Here’s what setting that up looks like.
+
+### Step 1: Generate a deploy token
+
+```
+fly tokens create deploy --app
+```
+
+Treat that token like a password. Copy it somewhere safe—you’ll need it for GitHub.
+
+### Step 2: Add it to your GitHub repo
+
+In GitHub:
+
+- Go to your repo > Settings > Secrets and variables > Actions
+- Add a new secret named `FLY_API_TOKEN`
+- Paste in the deploy token
+
+### Step 3: Write the workflow file
+
+Drop this into `.github/workflows/fly.yml` in your repo:
+
+```
+name: Fly Deploy
+
+on:
+ push:
+ branches:
+ - main # or your default branch
+
+jobs:
+ deploy:
+ name: Deploy app
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: superfly/flyctl-actions/setup-flyctl@master
+ - run: flyctl deploy --remote-only
+ env:
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+```
+
+Push that file, and your next `git push` to `main` will kick off a deployment. You can watch it happen in the Actions tab.
+
+The `--remote-only` flag tells `flyctl` to build your image on Fly.io’s remote builders, so your CI environment doesn’t need Docker.
+
+From here on out, you’ve got continuous deployment. Push to `main`, app goes live.
+
+A realistic example: you’re running a Rails app backed by Postgres and Redis, deployed to `iad` and `lhr`. It lives in a monorepo, and you want each push to `main` to trigger a deployment. The `flyctl` + GitHub Actions setup gets you continuous deployment. You'll handle the supporting infrastructure imperatively (think databases, IP addresses, scaling): `fly pg create`, `fly redis create`, `fly scale count`, `fly ips allocate`, and so on. The platform keeps track of what you’ve configured, so you don’t need to re-declare it every time.
+
+
+**Tip:** If you want a repeatable setup, you can script your commands into a bash file. Just be aware: that script will be linear and imperative, not declarative or idempotent like a Terraform plan.
+
+
+---
+
+## The harder way: The Machines API
+
+This is the route for teams who were doing more than `terraform apply`. If you had logic layered into your infra automation like canary deploys, ephemeral environments, controlling exactly which VM runs where, you’ll want to look at the Machines API. It’s lower-level, but more powerful. Think of it as Fly.io’s bare-metal interface. No `plan`, no `state`, no `graph`, just HTTP.
+
+You can use it to:
+
+- Spin up new Machines in specific regions
+- Roll out changes a Machine at a time
+- Build custom scaling logic
+- Tear down and rebuild environments on demand
+
+This is what Fly.io’s own orchestration tools use under the hood. You can spin up Machines, wire them together, and orchestrate them across regions, just like we do. You've got all the same knobs and levers we use internally, and you can build exactly the kind of workflow your setup needs.
+
+### What you're working with
+
+The Machines API is a REST interface. It speaks JSON over HTTPS. You authenticate using a [Fly.io API token](https://fly.io/docs/security/tokens/), ideally a deploy token scoped to your app or organization. Pass it as a `Bearer` token in the `Authorization` header. If you've used GitHub Actions to deploy with `flyctl`, it's the same kind of token.
+
+Each Fly App is a namespace. You can create as many Machines (VMs) as you want in an App. Each Machine runs a Docker image, has a region, some resource settings, and a lifecycle: create → start → stop → destroy.
+
+You don’t need special tooling to use it. `curl` works. So does `fetch`, `requests`, `httpie`, or whatever HTTP client you like in Go or Rust or Python. Everything you might script with `flyctl`, like creating databases, setting scale counts, can also be done via the Machines API. It’s more verbose, but gives you full control and makes it easier to integrate infrastructure setup into a larger orchestration flow.
+
+### Example: spin up a Machine with curl
+
+```
+export FLY_APP_NAME="your-app-name"
+export FLY_API_TOKEN="your-fly-token"
+
+curl -i -X POST \
+ -H "Authorization: Bearer ${FLY_API_TOKEN}" \
+ -H "Content-Type: application/json" \
+ "https://api.machines.dev/v1/apps/$%7BFLY_APP_NAME%7D/machines" \
+ -d '{
+ "region": "sjc",
+ "config": {
+ "image": "nginx",
+ "guest": {
+ "cpu_kind": "shared",
+ "cpus": 1,
+ "memory_mb": 256
+ }
+ }
+ }'
+```
+
+The response includes the ID of the new Machine. You can then start, stop, or destroy it by hitting `/machines/{id}` with the appropriate verb.
+
+### When to reach for the API
+
+Most apps don’t need this. But if you’re building something dynamic, like spinning up short-lived browser-based dev environments or launching ephemeral multiplayer game servers, then you’ll want this kind of control.
+
+Some of our favorite real-world uses:
+
+- A “run this one function on a new machine” pattern, demoed in [this JS repo](https://github.com/fly-apps/fly-run-this-function-on-another-machine). It boots a new Machine in-region, runs a job, and tears it down. Works like serverless, but with a full VM and no cold starts.
+- A minimal Go client for the Machines API, [`sosedoff/fly-machines`](https://github.com/sosedoff/fly-machines), has helpers to create, update, and destroy Machines programmatically. If you’re replacing Terraform logic with your own Go-based orchestration, this is a good lightweight example. For something more comprehensive, check out our official fly Go SDK, [`superfly/fly-go`](https://github.com/superfly/fly-go), for tighter integration with the platform.
+
+The Machines API doesn’t lock you into a flow, we just give you the primitives.
+
+**Still not sure? Here's the comparison.**
+
+| Feature | `flyctl` + GitHub Actions | Machines API |
+| --- | --- | --- |
+| Best for | Standard apps, CI/CD | Custom infra, dynamic environments |
+| Setup time | Fast | Slower |
+| Complexity | Low | High |
+| Control | Abstracted (`deploy`, `scale`) | Full (`create`, `start`, `destroy`) |
+| Official support | Fully supported & recommended | Fully supported & documented |
+
+## One last thing
+
+We know it sucks when a tool you relied on gets deprecated. Terraform made a lot of things feel clean and declarative. But when it comes to the Machines API specifically, Terraform was always working at a bit of a mismatch since it tries to express an inherently imperative lifecycle in a declarative model. The Machines API gives you fine-grained control and flexibility that didn’t map cleanly to Terraform's abstractions. With a little effort, you can build exactly what you need, and avoid being boxed in by someone else’s idea of what an app should look like.
+
+If you're rebuilding that tooling now, let us know what you're working on. We might be able to help, or at least learn something from it.
+
+## Related Reading
+
+- [Continuous Deployment with flyctl and GitHub Actions](https://fly.io/docs/launch/continuous-deployment-with-github-actions/) Walk‑through on wiring CI/CD with Fly.io + GitHub Actions.
+- [Access Tokens for Fly.io](https://fly.io/docs/security/tokens/) Deep dive into token scopes, permissions, and best practices for automation workflows.
+- [Working with the Machines API](https://fly.io/docs/machines/api/working-with-machines-api/) Reference guide for automating at the VM/“machine” level (the lower‑level alternative to flyctl).
+- [App Configuration (`fly.toml`)](https://fly.io/docs/reference/configuration/) Details on how your app config file works, which is often part of your automation setup.
\ No newline at end of file
diff --git a/blueprints/multi-region-fly-replay.html.md b/blueprints/multi-region-fly-replay.html.md
index 22621dd585..5f933657bd 100644
--- a/blueprints/multi-region-fly-replay.html.md
+++ b/blueprints/multi-region-fly-replay.html.md
@@ -1,12 +1,18 @@
---
title: Multi-region databases and fly-replay
layout: docs
-nav: firecracker
+nav: guides
---
+
+
+
+
+## Overview
+
You want to run your app and database in multiple [regions](/docs/reference/regions/) close to your users, and deploying database read replicas will give you better performance on read requests. This is a good solution for apps with read-heavy database workloads. But you also want writes to be efficient, despite needing to write to a primary database on the other side of the world.
-This blueprint will help you understand the concepts to implement an app, with a primary database and read replicas in multiple regions, that uses the `fly-replay` response header to re-route write requests to the primary region. Consider using this blueprint when:
+This guide will help you understand the concepts to implement an app, with a primary database and read replicas in multiple regions, that uses the `fly-replay` response header to re-route write requests to the primary region. Consider using this guide when:
- Your app's users are in more than one geographic region and you want low latency for reads in all regions.
- Your app has a request distribution of less than 50% writes.
@@ -98,14 +104,11 @@ Example implementations of this multi-region database with `fly-replay` pattern
- [Fly Ruby gem](https://github.com/superfly/fly-ruby+external) for running database replicas alongside your app instances in multiple regions.
- [Fly Postgres Elixir library](https://github.com/superfly/fly_postgres_elixir+external) for geographically distributed Elixir applications using Ecto and PostgreSQL in a primary/replica configuration on Fly.io.
-## Read more
+## Related reading
We've covered multi-region databases with `fly-replay` in some past blog posts and in our docs:
- [Globally Distributed Postgres](https://fly.io/blog/globally-distributed-postgres/)
-
- [Multi-Region Laravel with PlanetScale](https://fly.io/laravel-bytes/multi-region-laravel-with-planetscale/)
-
- [Run Ordinary Rails Apps Globally](https://fly.io/ruby-dispatch/run-ordinary-rails-apps-globally/)
-
-- [Dynamic Request Routing](/docs/networking/dynamic-request-routing/) docs
+- [Dynamic Request Routing](/docs/networking/dynamic-request-routing/)
\ No newline at end of file
diff --git a/blueprints/n-tier-architecture.html.md b/blueprints/n-tier-architecture.html.md
new file mode 100644
index 0000000000..17f7dd9600
--- /dev/null
+++ b/blueprints/n-tier-architecture.html.md
@@ -0,0 +1,130 @@
+---
+title: Getting Started with N-Tier Architecture
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-08-29
+---
+
+
+
+
+
+## Introduction: What is n-tier architecture?
+
+When people talk about “**n-tier architecture**,” they’re describing a way of splitting an app into layers (or “tiers”) that each have a specific job. The “**n**” just means there could be two, three, or more tiers depending on how you slice things.
+
+---
+
+## Defining tiers
+
+- **Presentation tier**: the UI/frontend of your app — HTML/CSS/JS in a browser, or a mobile app interface — that the user interacts with
+- **Web tier**: the app servers/backend that [handle requests](/docs/networking/dynamic-request-routing/) and run business logic
+- **Data tier**: stores, manages and provides access to application data
+
+---
+
+## N-Tier Examples
+
+### Two tiers
+
+**web app + database**. This is the most common case on Fly.io: your app servers run on Fly Machines and connect to a shared Postgres cluster.
+
+### Three tiers
+
+**frontend + backend + database**. You’ll see this if you have a separate frontend (like a React SPA served from a CDN, or a mobile app) that talks to your Fly backend.
+
+### Mapping to Fly.io
+
+- **Web tier** → your app running on Fly Machines
+- **Data tier** → [Fly Managed Postgres](/docs/mpg/) (our hosted Postgres that handles the messy parts for you)
+- **Optional presentation tier** → a separate frontend app (web or mobile) that talks to your Fly backend
+
+The idea is simple but powerful: the web tier doesn’t keep state. All shared state lives in the data tier, so you can add or remove servers at will without breaking anything. It’s the architecture you want if you care about **scalability** (handling more traffic by adding machines) and **reliability** (requests keep flowing even if one machine goes down).
+
+---
+
+## Why use n-tier on Fly.io?
+
+- **Scale with one command**: Add more Web/App machines whenever traffic spikes.
+- **Keep requests flowing**: Fly’s proxy automatically balances load across your Machines.
+- **Let the database do its job**: With [Managed Postgres](/docs/mpg/), you don’t need to worry about setup, backups, or failover.
+
+You could use Redis, Tigris, or other data stores here too, but if you’re not sure, start with Managed Postgres. It’s the default choice for good reason.
+
+---
+
+## Quickstart: Deploying a n-Tier App
+
+Let’s spin up a simple n-tier setup.
+
+### 1. Launch your app
+
+First, deploy your app to Fly:
+
+```bash
+fly launch
+fly deploy
+```
+
+This gives you the **web tier**.
+
+---
+
+### 2. Add a database (Managed Postgres)
+
+Now create your **data tier** with [Fly Managed Postgres](/docs/mpg/):
+
+```cmd
+fly mpg create
+```
+
+This will:
+
+- Provision a new Postgres cluster in the same region as your app to keep latency to a minimum (though you can choose another region).
+- Configure monitoring, daily backups, and automatic failover.
+
+To connect your app, you’ll need to add the database credentials as secrets. You can do this from the Fly.io Dashboard, or with `flyctl`:
+
+```cmd
+fly secrets set DATABASE_URL=postgres://:@/
+```
+
+Most frameworks (Rails, Django, Node, etc.) will automatically read `DATABASE_URL` from the environment. Once set, your app is ready to talk to Postgres.
+
+More details on creating and connecting a managed Postgres database can be found [here](/docs/mpg/create-and-connect/).
+
+---
+
+### 3. Scale out your web tier
+
+If traffic grows, add more Machines:
+
+```cmd
+fly scale count 3
+```
+
+[Fly’s proxy](/docs/reference/fly-proxy/) takes care of spreading requests across the machines (load balancing).
+
+**Note:** Scaling works best when your web tier is **stateless**. Any shared data like file uploads, user sessions/cookies, or cached state should live in your data tier (usually Managed Postgres, sometimes object storage or Redis). If your framework defaults to local disk or memory for these, configure it to use a shared store instead.
+
+---
+
+## Finishing up
+
+At this point you’ve got:
+
+- **Optional presentation tier**: a separate frontend app, if you use one (delivered to browsers or devices)
+- **Web tier**: Fly Machines running your app (stateless and load balanced)
+- **Data tier**: [Fly Managed Postgres](/docs/mpg/)
+
+Every request can hit any Machine, and all Machines share the same Postgres. That’s an n-tier architecture in action, and it’s the foundation for most reliable web apps.
+
+---
+
+## Related reading
+
+- Explore the features of [Fly Managed Postgres](/docs/mpg/)
+- Find out how to [scale apps on Fly](/docs/launch/scale-count/)
+- Read about [Fly networking](/docs/networking/)
+- Learn more about [n-tier architecture](https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture)
diff --git a/blueprints/observability-for-user-apps.html.md b/blueprints/observability-for-user-apps.html.md
new file mode 100644
index 0000000000..692c2dcee8
--- /dev/null
+++ b/blueprints/observability-for-user-apps.html.md
@@ -0,0 +1,121 @@
+---
+title: Observability for User Apps
+layout: docs
+nav: guides
+date: 2025-05-26
+---
+
+
+
+
+
+## Overview
+
+When you run a platform for users to execute their own code on Fly.io—[think per-user development environments](/docs/blueprints/per-user-dev-environments/) or AI/LLM apps—it's a good idea to have real-time observability for those user apps. You might _also_ want to stream logs from those apps back to the original developer (your end-user), so they can see what their code is doing.
+
+Fly.io offers built-in telemetry based on [**NATS**](https://nats.io/) that you can tap into to achieve this. At a high level, Fly.io captures everything your app writes to stdout and funnels it into an internal NATS log stream. A proxy in front of this NATS cluster ensures that each organization can only access its own applications' logs. Both the `flyctl logs` command and the our web dashboard's "Live Logs" use this pipeline under the hood. We can use the same system! By connecting a NATS client within your Fly organization's [private network](/docs/networking/private-networking/), we can subscribe to log subjects and stream logs out to wherever we need, in real-time.
+
+## Deploying the Fly Telemetry Forwarder
+
+If you want a bird's‑eye view for your own operations team, Fly provides an open source reference app called [**fly-telemetry**](https://github.com/superfly/fly-telemetry) that you can deploy to quickly get an observability stack running in your Fly organization. This app acts as a telemetry forwarder: it subscribes to the platform's NATS logs (and metrics) streams and stores the data for you. It includes a lightweight time-series database (VictoriaMetrics for metrics, VictoriaLogs for logs) and a Grafana instance with pre-configured dashboards, so you can explore your apps' logs and metrics visually.
+
+In an empty directory, run:
+
+```console
+fly launch --from https://github.com/superfly/fly-telemetry \
+ --yes \
+ --copy-config \
+ --org "$ORG" \
+ --env ORG="$ORG" \
+ --secret ACCESS_TOKEN="$(fly tokens create readonly "$ORG")" \
+ --flycast
+```
+
+After the deployment finishes, browse to the app's private URL [through WireGuard](/docs/blueprints/connect-private-network-wireguard/) or `fly proxy` and you have instant Grafana dashboards.
+
+This is handy for SREs, but not necessary for your end‑users.
+
+---
+
+## Streaming Fly app logs to your end users
+
+Streaming live logs straight to your users gives them insight into what their code is doing—developers see exactly what the runtime sees, in real time, without leaving your product or juggling extra tooling.
+
+### Connecting to the NATS Log Stream
+
+Every Fly organization has access to **NATS** which carries the logs for its apps. The server is available at the well-known address `[fdaa::3]:4223`. To connect to this endpoint, you must do so from within the Fly network—for example, from code running in a Fly app (like your telemetry forwarder or router) or from your local machine over a WireGuard connection.
+
+Using the NATS JavaScript client, you could connect as follows:
+
+```javascript
+import { connect, StringCodec } from "nats";
+
+const nc = await connect({
+ servers: "[fdaa::3]:4223",
+ user: "sandwich-expert", // your Fly org slug (example)
+ pass: process.env.ACCESS_TOKEN // your Fly access token (read-only)
+});
+
+// Now nc is a NATS connection that can subscribe to log subjects.
+```
+
+### Authentication
+
+When connecting from a Fly app in your org, you'll need to supply an access token. To generate one manually, run `fly tokens create readonly ` and store it as an app secrets. The NATS proxy will accept the token as the password and your org name as the username.
+
+### Log Subjects and Filtering
+
+Logs in the NATS stream are organized by **subject**. The subject format for Fly logs is:
+
+```console
+logs...
+```
+
+For example, if you have an application named `panini-786` and it's running in region `dfw`, you might see subjects like `logs.panini-786.dfw.d896d7c609dd68` (where `d896d7c609dd68` is the Machine ID). You can subscribe at varying levels of granularity using NATS wildcards:
+
+| Subject Pattern | Description |
+|----------------|-------------|
+| `logs.>` | Subscribe to **all logs** from all apps in your org (every region, every Machine) |
+| `logs..*.>` | Subscribe to all logs from a specific app (across all regions and Machine) |
+| `logs.*..>` | Subscribe to all logs from a specific region |
+| `logs...` | Subscribe to a single Machine (full exact subject) |
+
+In practice, to stream all logs for a given user's app, you'll subscribe to the subject pattern `logs..>` – this will capture any log line from any Machine of that app, in any region.
+
+### A Real-World Example
+
+[**NATstream**](https://natstream.fly.dev/) is a reference app that streams logs from your Fly apps right back to the browser. It connects to NATS and turns those events into a live feed at a simple HTTP `/logs` endpoint using Server-Sent Events (SSE). Developers could log in to your product and watch their app's logs flow in real time, without needing separate logging tools or the Fly CLI. The app keeps things high-level and lightweight—there's no deep framework complexity here, just a straightforward example of hooking into Fly's log pipeline and broadcasting events to the browser.
+
+
+
+
+
+You can fork it as a foundation for your own developer-facing live log streaming service, or rip out the relevant parts and build this logic into your existing app, using the NATstream implementation as a guide. More robustly, you could run a separate log "router" app that handles authentications and streaming on a per user, per app basis. This would be especially useful if streaming logs to a CLI.
+
+---
+
+## Building a Central Log Router Service
+
+Create a Fly "log router" app that sits between Fly's NATS telemetry stream, your product and your developers. Because each customer already has their own Fly app, every user's logs appear on a unique subject (`logs..>`), so multi-tenancy is solved by design—subscribe only to the subject that matches the requesting developer's app and you'll never mix data between users.
+
+
+**Note**: Your router service should enforce that a user can only request the logs for the app(s) they own. This means your routing logic needs an access control check (e.g. based on the authenticated user's ID matching the app name or an internal mapping). Fortunately, if you've structured your platform as one Fly app per user, it's straightforward to maintain a mapping from user account to Fly app name and use that for authorization.
+
+
+On startup the router opens one NATS connection with an org-scoped read-only token. When a developer signs in through your dashboard or CLI, the router authenticates that user, determines their Fly app name, and, if not already listening, subscribes to that app's subject. As log entries arrive, the router forwards them over a live channel—typically a websocket or Server-Sent Events stream—directly to the developer's browser or CLI.
+
+The router itself is a Fly app that does the following:
+
+- On startup, connect to the NATS log stream using the org-wide credentials (as shown earlier).
+- Wait for developers to request or connect for log streaming (e.g., a developer opens a web UI to view their app's live logs, or runs a command to view logs).
+- When a request for a specific app's logs is received, subscribe to that app's log subject (if not already subscribed).
+- Stream the incoming log messages to the requesting developer in real-time.
+- Ensure that each developer only receives their own app's logs. The router should use the mapping of app ← → user to publish the right data to the correct output channel (and not mix different users' data).
+
+This approach uses one NATS subscription _per user app_. The messages are forwarded verbatim to the user's websocket in this case (as JSON strings), but you could transform them (for example, format them nicely or filter out certain internal logs) before sending. On the client side, you would simply read from the websocket or SSE stream and append new log lines to a console view.
+
+## Related reading
+
+- [Logging overview](/docs/monitoring/logging-overview/) How logs flow from your Fly apps into the platform’s NATS pipeline.
+- [Logs API options](/docs/monitoring/logs-api-options/) Guide with approaches for tapping directly into structured logs programmatically.
+- [fly-telemetry](https://github.com/superfly/fly-telemetry) Lightweight reference implementation for quick, out-of-the-box observability for deployments on Fly.io.
\ No newline at end of file
diff --git a/blueprints/opensshd.html.md b/blueprints/opensshd.html.md
index 8c4d94f02f..dadc86d502 100644
--- a/blueprints/opensshd.html.md
+++ b/blueprints/opensshd.html.md
@@ -1,7 +1,7 @@
---
title: Run an SSH server
layout: docs
-nav: firecracker
+nav: guides
author: rubys
categories:
- SSH
@@ -9,6 +9,12 @@ date: 2024-01-14
redirect_from: /docs/app-guides/opensshd/
---
+
+
+
+
+## Overview
+
A number of tools allow you to interact with your server over SSH. These tools are useful for tasks such as
copying files ([rsync](https://rsync.samba.org/+external), [scp](https://en.wikipedia.org/wiki/Secure_copy_protocol+external), [sshfs](https://github.com/libfuse/sshfs#sshfs+external)),
editing ([emacs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Remote-Files.html+external), [vim](https://www.vim.org/scripts/script.php?script_id=1075+external), [vscode](https://code.visualstudio.com/docs/remote/ssh+external)), and
@@ -204,3 +210,8 @@ Additionally, if you want to avoid the fingerprint checking, you can add the fol
```config
StrictHostKeyChecking no
```
+
+## Related reading
+
+- [`flyctl` SSH commands](https://fly.io/docs/flyctl/ssh/) Official reference for how to use `fly ssh`/`fly ssh sftp` to access running machines.
+- [Multiple processes inside a Fly.io app](https://fly.io/docs/app-guides/multiple-processes/) Explains how to run multiple process groups in a single machine/container. Relevant when you’re adding `sshd` alongside your main app.
\ No newline at end of file
diff --git a/blueprints/per-user-dev-environments.html.md b/blueprints/per-user-dev-environments.html.md
new file mode 100644
index 0000000000..5321ebc3aa
--- /dev/null
+++ b/blueprints/per-user-dev-environments.html.md
@@ -0,0 +1,69 @@
+---
+title: Per-User Dev Environments with Fly Machines
+layout: docs
+nav: guides
+date: 2025-04-02
+---
+
+
+
+
+
+## Overview
+
+Fly Machines are fast-launching VMs behind [a simple API](https://fly.io/docs/machines/api), enabling you to launch tightly isolated app instances in milliseconds [all over the world](https://fly.io/docs/reference/regions/).
+
+One interesting use case: running isolated dev environments for your users (or robots). Fly Machines are a safe execution sandbox for even the sketchiest user-generated (or LLM-generated) code.
+
+This guide explains how to use Fly Machines to securely host ephemeral development and/or execution environments, complete with [dynamic subdomain routing](/docs/networking/dynamic-request-routing) using `fly-replay`.
+
+## What your architecture should include
+
+- **Router app(s)**
+ - A Fly.io app to handle requests to wildcard subdomains (`*.example.com`). Uses `fly-replay` headers to transparently redirect each request to the correct app and machine. If you have clusters of users (or robots) in different geographic regions, you can spin up a router app in multiple regions. See [Connecting to User Machines](/docs/blueprints/connecting-to-user-machines/) for details on how to implement the routing pattern.
+- **User apps (pre-created)**
+ - Dedicated per-user (or per-robot) Fly apps ([more about why you should create a dedicated app per customer/robot](/docs/machines/guides-examples/one-app-per-user-why)), each containing isolated Fly Machines. App and Machine creation is not instantaneous, so we recommend provisioning a pool of these before you need them so you can quickly assign upon request.
+- **Fly Machines (with optional volumes)**
+ - Fast-launching VMs that can be attached to persistent [Fly Volumes](/docs/volumes).
+
+### Example Architecture Diagram
+
+
+
+### Router app(s)
+
+Your router app handles all incoming wildcard traffic. Its responsibility is simple:
+
+- Extract subdomains (like `alice.example.com` → `alice-123`).
+- Look up the correct app (and optionally machine ID) for that user.
+- Issue a `fly-replay` header directing the Fly Proxy to [internally redirect the request](/docs/blueprints/connecting-to-user-machines/#using-fly-replay) (this should add no more than ~10 milliseconds of latency if the router app is deployed close to the user).
+- When appropriate, use [replay caching](/docs/networking/dynamic-request-routing/#replay-caching) to further reduce latency and load on the router app.
+- Make sure you've added [a wildcard domain](/docs/networking/custom-domain/#get-certified) (*.example.com) to your router app (read more about the [certificate management endpoint here](/docs/networking/custom-domain-api/)).
+
+### User apps
+
+Creating apps dynamically for each user at request time can be slow. To ensure fast provisioning:
+
+- **Pre-create** a pool of Fly apps and machines ahead of time (using the [Fly Machines API or CLI](/docs/apps/overview/)).
+- Store app details (e.g., app_name: `alice-123`) in a datastore accessible to your router app.
+- Assign apps to users at provisioning time.
+
+**Fly Machines**
+
+You'll want to spin up at least one Machine per user app (but apps can have as many Machines as needed). If your dev environments need persistent storage (data that should survive Machine restarts):
+
+- Attach Fly Volumes to each machine at creation time.
+- Keep in mind that machine restarts clear temporary filesystem state but preserve volume data.
+- Learn more about the [Machines API resource](/docs/machines/api/machines-resource/) and the [Volumes API resource](/docs/machines/api/volumes-resource/).
+
+## Pointers & Footguns
+
+- **Machines & volumes are tied to physical hardware:** hardware failures can destroy machines and attached volumes. **Always persist important user data** (code, config, outputs) to external storage (like [Tigris Data](/docs/tigris/#main-content-start) or AWS S3).
+- **Your users will break their environments:** pre-create standby machines to handle hardware & runtime failures, or the inevitable user or robot poisoned environment. Pre-create standby machines that you can quickly activate in these scenarios.
+- **Machine restarts reset ephemeral filesystem:** the temporary Fly Machine filesystem state resets on Machine restarts, ensuring clean environments. However, volume data remains persistent, making it useful for retaining user progress or state.
+
+## Related reading
+
+- [Connecting to User Machines](/docs/blueprints/connecting-to-user-machines/) How to manage routing and networking when you spin up a fleet of machines dedicated to individual users.
+- [Multi‑container Machines](/docs/machines/guides-examples/multi-container-machines/) When your per‑user environment needs sidecars (logs, metric exporters, agent processes) alongside the main service.
+
diff --git a/blueprints/private-applications-flycast.html.md b/blueprints/private-applications-flycast.html.md
index 4910ea65fc..932efdacda 100644
--- a/blueprints/private-applications-flycast.html.md
+++ b/blueprints/private-applications-flycast.html.md
@@ -2,7 +2,7 @@
title: Run private apps with Flycast
layout: docs
sitemap: true
-nav: firecracker
+nav: guides
author: xe
categories:
- networking
@@ -11,7 +11,7 @@ date: 2024-06-17
-## Intro
+## Overview
A lot of the time your applications are made to be public and shared with the world. Sometimes you need to be more careful. When you deploy your apps on Fly.io, you get a private network for your organization. This lets your applications in other continents contact each other like they were in the same room.
@@ -190,12 +190,10 @@ It took a moment for Ollama to get ready and download the image, then it downloa
And there we go! We’ve covered what Flycast is, why you’d want to use it, and set up an instance of Ollama to show it off.
-## Read more
+## Related reading
We've talked about Flycast in some past blog posts and blueprints:
- [Autostart and autostop private apps](/docs/blueprints/autostart-internal-apps/)
-
- [Deploy Your Own (Not) Midjourney Bot on Fly GPUs](https://fly.io/blog/not-midjourney-bot/)
-
- [Scaling Large Language Models to zero with Ollama](https://fly.io/blog/scaling-llm-ollama/)
diff --git a/blueprints/remote-mcp-servers.html.md b/blueprints/remote-mcp-servers.html.md
new file mode 100644
index 0000000000..90c5f2b01c
--- /dev/null
+++ b/blueprints/remote-mcp-servers.html.md
@@ -0,0 +1,75 @@
+---
+title: Deploying Remote MCP Servers
+layout: docs
+nav: guides
+date: 2025-04-15
+---
+
+
+
+
+
+## Overview
+
+The Model Context Protocol (MCP) is a fun new way to give LLMs new powers. Originally developed by Anthropic, the protocol has since been adopted by OpenAI (with Google Gemini support in the works at the time of writing).
+
+The protocol defines a standardized way of connecting tools and providing additional context to LLMs, not dissimilar to the way USB provides a standardized way to connect computers to peripherals and devices. Fly Machines are tightly isolated VMs that are perfect for running MCP servers.
+
+This guide will help you understand, at a very high level, how to build, deploy, and connect remote MCP servers on Fly.io. Keep in mind that this is an nascent, still-emerging protocol – details are subject to change. For specific implementation details, the [Model Context Protocol](https://modelcontextprotocol.io/) site is the authoritative source of truth (complete with [a handy .txt version for LLM prompting](https://modelcontextprotocol.io/llms-full.txt)).
+
+We've also started to integrate some MCP tooling into the `flyctl` that should give you a more concrete sense of how you might deploy MCP servers on Fly.io (read more in [this community forum post](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)). Note that our MCP implementation is experimental and may still have sharp edges!
+
+## Remote MCP Servers
+
+MCP to date has been largely local-only, but the protocol also [specs out transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) for remote MCP servers. Remote MCP servers solve a bunch of problems:
+
+- Easier to update a centralized server instead of dispersed, local packages
+- You can give MCP clients persistent connections that don't evaporate when someone closes their laptop
+- Securely sandbox MCP server activity in case the robots go rogue
+
+## Single Tenant or Multi-tenant
+
+There are broadly two patterns you might want to follow when deploying a remote MCP server to Fly.io:
+
+1. Multi-tenant MCP server (one app, many users)
+2. Single-tenant MCP servers (one app per user)
+
+We're partial to the single-tenant pattern – it ensures proper isolation, and also helps with minimize your Fly.io bill: unused Machines can stop and start as needed, so you won't waste resources on idle users (more about why we think one app per customer is the right pattern [here](https://fly.io/docs/machines/guides-examples/one-app-per-user-why/)). `fly-replay` makes it easy to route requests to the correct app / Fly Machine (see more detail about this pattern in [Per-user Dev Environments with Fly Machines](https://fly.io/docs/blueprints/per-user-dev-environments/)).
+
+## Multi-tenant MCP Servers
+
+
+
+You'll need two main components:
+
+1. **MCP-Remote Shim (optional)**: Tiny client-side proxy that connects local MCP clients to your remote servers (only needed if the MCP client doesn't support auth and / or streamable HTTP requests to a remote MCP server). Handles authentication via a secret shared between the shim and the router app (authentication could be a simple API token, username + password, or a full OAuth dance). Securely stores and refreshes tokens as needed. To see an example, try the experimental `fly mcp proxy` command in the `flyctl`. This sets up a local proxy that forwards MCP client requests to a remote URL (more details in [the docs](https://fly.io/docs/flyctl/mcp) and [the community forum](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)).
+2. **MCP Server App**: This runs the actual MCP goodness and authenticates requests from the MCP client. Should have a single streamable HTTP endpoint path for MCP client connections, as well as any specific business logic or other integrations. To try our implementation, include the experimental `fly mcp wrap` command in the Dockerfile for your MCP server app. This instantiates a lightweight HTTP server to receive requests forwarded from the local MCP client via `fly mcp proxy` (more details in [the docs](https://fly.io/docs/flyctl/mcp) and [the community forum](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)).
+
+## Single-tenant MCP Servers
+
+
+
+There are three main components:
+
+1. **Router App**: Receives requests from the MCP client and handles auth, then routes requests to per-user apps + Fly Machines with `fly-replay`. See [Connecting to User Machines](/docs/blueprints/connecting-to-user-machines/) for details on implementing the routing pattern. Optionally handles user management and permissions. You can use the experimental `fly mcp wrap` command in the Dockerfile of your router app to instantiate a lightweight HTTP server to receive requests forwarded from a local MCP client (more details in [the docs](https://fly.io/docs/flyctl/mcp) and [the community forum](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)). Note that `fly mcp wrap` does not handle request routing – you'll have to implement that separately.
+2. **MCP Server Apps**: Per-user (or per-team) apps that run the actual MCP goodness. Should have a single streamable HTTP endpoint path for MCP client connections, as well as any specific business logic or other integrations.
+3. **MCP-Remote Shim (optional)**: Tiny client-side proxy that connects local MCP clients to your remote servers (only needed if the MCP client doesn't support auth and / or streamable HTTP requests to a remote MCP server). Handles authentication via a secret shared between the shim and the router app (authentication could be a simple API token, username + password, or a full OAuth dance). Securely stores and refreshes tokens as needed. To see an example, try the experimental `fly mcp proxy` command in the `flyctl`, which sets up a local proxy that forwards MCP client requests to a remote URL (more details in [the docs](https://fly.io/docs/flyctl/mcp) and [the community forum](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)).
+
+## How Users Experience It
+
+From your users' perspective, the experience should be delightfully simple:
+
+1. They add a new MCP server to their MCP client with a simple one-liner
+2. If needed, the user is walked through an authentication flow (ie a browser window pops open to kick off the OAuth dance or provide an API key)
+3. After logging in, the MCP client can now access all the tools you've exposed
+4. The connection persists across restarts without re-authentication
+
+## Taking It Further
+
+If you'd like your MCP servers to connect to third party OAuth APIs without having to directly handle API tokens, consider using [ssokenizer](https://github.com/superfly/ssokenizer) (or [tokenizer](https://github.com/superfly/tokenizer) for generic, non-OAuth flows).
+
+## Related reading
+
+- [Model Context Protocol (MCP) Documentation](/docs/mcp/) Learn more in our reference for MCP: what it is, how it works, and how to deploy servers.
+- [Connecting to User Machines](/docs/blueprints/connecting-to-user-machines/) Find out about patterns for routing from a central router app to per‑user apps/machines via `fly‑replay`, which is useful when you build single‑tenant MCP setups.
+- [Launching MCP Servers with flyctl](/docs/flyctl/mcp-server/) Read about the experimental `fly mcp server` command: how to start a local or remote MCP server using `flyctl`, including flags and usage.
diff --git a/blueprints/resilient-apps-multiple-machines.html.md b/blueprints/resilient-apps-multiple-machines.html.md
index 4119abf92c..719e8f2bde 100644
--- a/blueprints/resilient-apps-multiple-machines.html.md
+++ b/blueprints/resilient-apps-multiple-machines.html.md
@@ -1,13 +1,46 @@
---
title: Resilient apps use multiple Machines
layout: docs
-nav: firecracker
+nav: guides
+date: 2025-09-12
---
-Fly Machines are fast-launching VMs; they're the compute of the Fly.io platform. Every Machine runs on a single physical host. If that host fails, the Machine becomes unavailable; it does not automatically get rescheduled on another host.
+
+
+
+
+
+**Fly Machines are fast-launching VMs, the basic compute unit of the Fly.io platform. Each Machine runs on a single physical host. If that host fails, the Machine goes down and does not automatically start again on another host.**
+
+
+## Overview
To make your app resilient to single-host failure, create at least two Machines per app or process. Fly Proxy autostop/autostart and standby Machines are built-in platform features that you can use to start extra Machines only when needed.
+---
+
+## Resiliency: Why and When
+
+Why resiliency? Machines are great until they aren’t. Hosts fail. Regions go dark. Networks get flaky. If your app only runs on one Machine, any of those failures means downtime. Running more than one Machine, or spreading them across regions, is how you keep the lights on when the infrastructure underneath you stumbles.
+
+### When to use it
+
+If you are building something people rely on, you probably want some resiliency. That includes production apps with paying users, customer-facing services where downtime makes people angry, or global apps that need to respond quickly no matter where users are. Regulations can also push you here if you are required to keep systems up in specific geographies.
+
+### When it might not be worth it
+
+For some apps, downtime just does not matter very much. A side project, a staging environment, or an internal tool might run fine on a single Machine. If the stakes are low, adding extra Machines can be more complexity than you need.
+
+### Performance
+
+Machines are really fast to start and stop. If your app has a lot of users, or bursts of high usage, then the Fly Proxy can load balance requests and automatically stop and start Machines based on traffic to your app. Keep one or more Machines running all the time if your app needs even faster first response times.
+
+### Costs
+
+You pay only for `rootfs` when Machines aren't running, not for CPU and RAM. Machine rootfs is [cheap](/docs/about/pricing/#stopped-fly-machines), like 18 cents a month for an average Elixir app that uses 1.2 GB of rootfs.
+
+---
+
## Multiple Machines for apps with services
You can add more Machines for Fly Proxy to start and stop as needed, which is great for apps that have built-in replication or that don't share data.
@@ -42,7 +75,7 @@ Use [Fly Proxy autostop/autostart](/docs/launch/autostop-autostart/) to automati
Fly Proxy uses the concurrency `soft_limit` to determine if Machines have excess capacity. Learn more about [how Fly Proxy autostop/autostart works](/docs/reference/fly-proxy-autostop-autostart/).
-**Using the Machines API:** To add or change the autostop/autostart settings with the Machines API, use the settings in the `services` object of the [Machine config](/docs/machines/api/machines-resource/#machine-config-object-properties) in your create or update calls.
+**Using the Machines API:** To add or change the autostop/autostart settings with the Machines API, use the settings in the `services` object of the [Machine config](/docs/machines/api/machines-resource/#machine-config-object-properties) in your `create` or `update` calls.
#### 2. Create more Machines
@@ -62,11 +95,13 @@ Learn more about [scaling the number of Machines](/docs/apps/scale-count/).
**Using the Machines API:** To scale the number of Machines with the Machines API, you can "clone" Machines. Get and modify a Machine config and use that config to create new Machines in any region. See the[ Machines resource docs](/docs/machines/api/machines-resource/).
+---
+
## Standby Machines for apps without services
When apps or processes are running tools like cron that don't require local storage or accept external requests, it's common to run only one Machine. Since these Machines don't have services configured, they can't be automatically started and stopped by the Fly Proxy. To add redundancy against host failures for this kind of Machine, use a standby Machine; it stays stopped and ready to take over in case the original Machine becomes unavailable.
-Unlike Fly Proxy autostop/autostart, which starts Machines based on app traffic, a standby Machine watches the Machine it's paired to and starts only if that Machine becomes unavailable. Learn more about [standby Machines](https://fly.io/docs/reference/app-availability/#standby-machines-for-process-groups-without-services).
+Unlike Fly Proxy autostop/autostart, which starts Machines based on app traffic, a standby Machine watches the Machine it's paired to and starts only if that Machine becomes unavailable. Learn more about [standby Machines](/docs/reference/app-availability/#standby-machines-for-process-groups-without-services).
### You get a standby Machine on first deploy
@@ -84,7 +119,7 @@ fly machine clone --standby-for
**Using the Machines API:** To create a standby Machine with the Machines API, get and modify your worker Machine config and use that config to create a new standby Machine. Specify the standby in the Machine's config object: [config.standbys](/docs/machines/api-machines-resource/#machine-config-object-properties).
-## Bonus uses for standby Machines
+### Bonus uses for standby Machines
A standby Machine doesn't need to be a copy of the Machine it's watching. You can configure the standby Machine to run with any image you like when a watched Machine becomes unavailable; you can use it to run a recovery script, trigger alerts, or anything else. You can create a standby Machine for any Machine, including one with services.
@@ -94,15 +129,47 @@ To create a standby Machine from a different image:
fly machine run --standby-for
```
-## Benefits and cost of multiple Machines
+---
+
+## Scaling to multiple regions
+
+Running multiple Machines in one region protects you from host failures. But entire regions can fail too. Power cuts, network issues, and upstream outages do not happen often, but when they do, every Machine in that region goes down. If your app needs to weather that kind of failure, the next step is spreading out across regions.
+
+### When to use it
+
+Multi-region setups make sense when uptime really matters. That might mean production systems with strict SLAs, customer-facing apps where downtime causes pain, or global services that need to stay responsive close to users. Regulations can also be a factor if you need to keep infrastructure running in specific geographies.
+
+### Example
+
+You can place Machines in more than one region with a single command:
+
+```
+fly scale count 6 --region ams,ewr,syd
+```
+
+This example runs two Machines in Amsterdam, two in New Jersey, and two in Sydney. Fly Proxy will route users to the closest healthy region.
+
+One important caveat: volumes do not replicate across regions. If your app writes data, you need to think carefully about where that data lives and how it syncs. A common approach is to keep one primary “write” region and treat others as read-only.
+
+### Keep in mind
+
+Multi-region comes with real advantages, but also some trade-offs. Users near your regional Machines will see faster responses, but coordinating data across regions is hard, especially since most apps are not designed for global writes. And while redundancy improves reliability, it also increases your costs: more Machines in more places means more expense, and cross-region traffic can add up quickly.
+
+---
+
+## Summing up
+
+Resiliency builds in layers. At the base, you have a single Machine: cheap, simple, and fragile. Add a second Machine in the same region and you are protected from host failures. Spread Machines across regions and you are resilient to outages that take down an entire site, with the added bonus of faster responses for users around the world.
-**Resiliency:** Your app won't fail when a single host fails because another Machine will start up to take over app requests or tasks.
+Each layer improves availability, but it also adds cost and complexity. The right choice depends on what you are building and who is depending on it.
-**Performance:** Machines are really fast to start and stop. If your app has a lot of users, or bursts of high usage, then the Fly Proxy can load balance requests and automatically stop and start Machines based on traffic to your app. Keep one or more Machines running all the time if your app needs even faster first response times.
+Whatever strategy you choose, practice it. Do not wait for a real outage to find out how your app behaves. Stop a Machine and see if traffic shifts. Kill a region and watch how your data layer responds. A resiliency plan only works if you have rehearsed it enough to trust it.
-**Cost**: You pay only for rootfs when Machines aren't running, not for CPU and RAM. Machine rootfs is [cheap](/docs/about/pricing/#stopped-fly-machines), like 18 cents a month for an average Elixir app that uses 1.2 GB of rootfs.
+## Related reading
-## Read more
+If you want to dig deeper into how Fly apps stay available and what failure modes to expect, these are good next steps:
-- [App availability and resiliency](/docs/reference/app-availability/): An overview of the all features that can make your app more resistant to events like hardware failures or outages.
-- [Troubleshoot apps when a host is unavailable](/docs/apps/trouble-host-unavailable/): What we hope you won't have to do, because you followed this blueprint and have multiple Machines.
+- [App availability and resiliency](/docs/apps/app-availability/): An overview of the all features that can make your app more resistant to events like hardware failures or outages.
+- [Multi-region with Fly-Replay](/docs/blueprints/multi-region-fly-replay/): How to serve reads close to users and route writes back to a primary region.
+- [Machine placement and regional capacity](/docs/machines/guides-examples/machine-placement/): How Fly decides where to place your Machines, how to guide placement yourself, and what happens when a region runs out of capacity.
+- [Run Ordinary Rails Apps Globally](/ruby-dispatch/run-ordinary-rails-apps-globally/): Project-level example of Rails distributed across regions with smart routing.
diff --git a/blueprints/review-apps-guide.html.md b/blueprints/review-apps-guide.html.md
index d19fa2f413..d59e192be3 100644
--- a/blueprints/review-apps-guide.html.md
+++ b/blueprints/review-apps-guide.html.md
@@ -1,7 +1,7 @@
---
title: "Git Branch Preview Environments on Github"
layout: docs
-nav: firecracker
+nav: guides
categories:
- ci
- github
@@ -11,6 +11,12 @@ categories:
redirect_from: /docs/app-guides/review-apps-guide/
---
+
+
+
+
+# Overview
+
Review apps are a great way to preview new features, changes, and bug fixes. This guide will teach you how to automatically generate ephemeral "review apps" on Fly.io for each pull request (PR) using GitHub Actions. This approach can be applied to other Git services and source code versioning software that supports branching and hooks.
## Quick Start
@@ -163,3 +169,7 @@ Once you have your secrets and environment variables set in GitHub, add each of
with:
secrets: FOOBAR=${{ secrets.FOOBAR }} SOME_API_KEY=${{ secrets.SOME_API_KEY}}
```
+## Related Reading
+
+- [Continuous Deployment with Fly.io and GitHub Actions](/docs/launch/continuous-deployment-with-github-actions/) Shows how to set up automated CI/CD pipelines using GitHub Actions and Fly.io — a natural complement to per‑PR review deployments.
+- [Get information about an app](/docs/apps/info/) A handy guide to inspecting apps, machines, statuses via CLI/API — useful when you’re creating and destroying review apps on pull requests.
\ No newline at end of file
diff --git a/blueprints/rollback-guide.html.md b/blueprints/rollback-guide.html.md
new file mode 100644
index 0000000000..89ea2ab437
--- /dev/null
+++ b/blueprints/rollback-guide.html.md
@@ -0,0 +1,134 @@
+---
+title: Rollback Guide
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-08-22
+---
+
+
+
+
+
+## Overview: Why rollback?
+
+Fly.io runs your apps close to your users, in VMs you can spin up and tear down in seconds. But what if you spin up something broken? If a deploy goes wrong, it’s easy to roll back: just tell Fly to redeploy a previous image. There’s no special `rollback` command because you don’t need one. Rollbacks use the same deploy mechanism you already know.
+
+If you know which image was working before, you can deploy it again. You just need to know where to find the image, and how to tell Fly to use it. The trick is: you’re rolling back the _VM image_, not the database. If you ran migrations in that bad deploy, you’ll have to undo those separately in your application or migration code. Fly isn’t going to time-travel your data.
+
+---
+
+## How to rollback
+
+### Step 1: See what you’ve shipped
+
+Fly keeps a history of your releases, including the exact container image each one used. Run:
+
+```bash
+fly releases --app myapp --image
+```
+
+You’ll see a list of past releases with timestamps and image tags:
+
+```
+v42 2025-08-12T16:21Z registry.fly.io/myapp:deployment-01HXXXXX
+v41 2025-08-10T09:14Z registry.fly.io/myapp:deployment-01HYYYYY
+```
+
+Pick the one before things went sideways.
+
+---
+
+### Step 2: Deploy that exact image
+
+Let’s say `v41` was fine. You can redeploy its image directly:
+
+```bash
+fly deploy --image registry.fly.io/myapp:deployment-01HYYYYY
+```
+
+That’s it — no rebuild, no code checkout, just telling Fly to boot the older VM image. This works as long as the image is still in the Fly registry.
+
+---
+
+### Tip: Stop living in `deployment-01HXXXXX` hell: use human-readable tags
+
+The default auto-generated tags work for machines, not humans. You can make your life easier by tagging builds with something you’ll recognize later — a git commit hash, a version number, or even just `before-i-broke-prod`.
+
+When you deploy, pass `--image-label` to set a tag:
+
+```bash
+fly deploy --image-label v1.2.3
+```
+
+That command:
+
+1. Builds your app
+1. Tags the image as `v1.2.3`
+1. Deploys it
+
+Rolling back later becomes:
+
+```bash
+fly deploy --image registry.fly.io/myapp:v1.2.3
+```
+
+---
+
+## How long does Fly keep your images?
+
+Right now, Fly doesn’t promise to store app images forever. If an image hasn’t been deployed for a while, it might be pruned from our registry to free up space. If you need a “guaranteed forever” rollback target, push your image to a public container registry (like Docker Hub, GHCR, or ECR) and deploy from there. See [Using the Fly Docker Registry](https://fly.io/docs/blueprints/using-the-fly-docker-registry/#to-deploy-a-public-image-from-docker-hub-or-another-public-registry) for details.
+
+---
+
+## Things to watch out for
+
+Rollbacks are simple in Fly, but they aren’t magic. A few details are worth keeping in mind:
+
+- **Database schema drift** → If a deploy ran destructive migrations (dropping a column, renaming a table), rolling back the app image might not help. Plan migrations so they’re safe both forward and backward.
+- **Image retention** → Images don’t live forever in Fly’s registry. For long-term rollback insurance, push builds to your own container registry.
+- **Config, Secrets and `fly.toml`** → Rollbacks don’t undo config changes. When you deploy an older image, Fly still uses your current `fly.toml`, along with whatever env vars and secrets are set now. If a past deploy depended on an older config, you’ll need to update those settings manually when rolling back.
+- **Autoscaling** → If your app scaled up during the bad release (say traffic spiked and Fly added more machines), those extra machines don’t vanish the instant you roll back. They’ll hang around until autoscaling decides to scale them down. This is expected behavior and your rollback still worked; you just have more machines than usual for a while. Read more about [metrics-based autoscaling](https://fly.io/docs/launch/autoscale-by-metric/).
+- **Monitoring** → Always confirm a rollback stabilized things with logs , probes (`fly logs`, `fly status,` Grafana metrics), not just the success message from `fly deploy`.
+- **Regions** → By default, rollbacks redeploy globally. If you run in multiple regions (say `iad`, `lhr`, `syd`), you might test a rollback in just one region before rolling back everywhere. You can do this with the `--region` flag:
+
+```bash
+fly deploy --image registry.fly.io/myapp:v1.2.3 --region iad
+```
+---
+
+## Tip: Faster rollbacks with deploy strategies
+
+By default, Fly uses `rolling` deploys: new machines come up before old ones shut down. That’s safe for upgrades, but if you know the current release is bad, you may want a quicker cutover.
+
+### All-at-once rollback:
+
+```bash
+fly deploy --image registry.fly.io/myapp:v1.2.3 --strategy immediate
+```
+
+This stops old machines and boots the rollback image as quickly as possible.
+
+
+
+### Controlled speedup:
+
+```bash
+fly deploy --image registry.fly.io/myapp:v1.2.3 --max-unavailable 0.5
+```
+
+This lets you take down up to half your machines at once, moving faster than a cautious rolling deploy while avoiding a full stop.
+
+Use `--strategy immediate` when speed matters more than downtime, and `--max-unavailable` when you want a hedge between safety and speed.
+
+---
+
+## Summing up
+
+Rolling back on Fly is basically just _deploying an older image_. Once you’ve got the muscle memory for `fly releases --image` and `fly deploy --image`, it’s a quick, predictable way to recover from bad deploys. And if you start tagging images with human-readable labels, you’ll have an easier time remembering which one to trust when production is on fire.
+
+## Related reading
+
+- [Seamless Deployments on Fly.io](/docs/blueprints/seamless-deployments/) How to set up deploys that avoid downtime and make rollbacks less painful.
+- [Using the Fly Docker Registry](/docs/blueprints/using-the-fly-docker-registry/) Details on image tagging and retention — key context for understanding how rollbacks work.
+- [Staging and Production Isolation](/docs/blueprints/staging-prod-isolation/) Tips on environment isolation that reduce the blast radius of a bad release.
\ No newline at end of file
diff --git a/blueprints/seamless-deployments.html.md b/blueprints/seamless-deployments.html.md
new file mode 100644
index 0000000000..2c15d12eb1
--- /dev/null
+++ b/blueprints/seamless-deployments.html.md
@@ -0,0 +1,108 @@
+---
+title: Seamless Deployments on Fly.io
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-09-23
+---
+
+
+
+
+
+## Overview
+
+
+**Zero-downtime deploys aren’t magic. They’re just health checks that work. Fly.io apps run on individual VMs, and we won’t start routing traffic to a new one until it proves it’s alive. Here's how to get seamless deploys without breaking things or relying on hope as a strategy.**
+
+
+## Your deploy is only as good as your health checks
+
+Fly.io deployments look like magic when health checks are working. New Machines boot. Old ones go down. Users never notice. But behind the scenes, the [Fly Proxy](/docs/reference/fly-proxy/) is watching your Machines and only sending them traffic if they pass your health checks.
+
+[Health checks](/docs/reference/health-checks/) are how you tell the platform that your app is alive. If they're missing or broken, you're flying in the dark.
+
+In your `fly.toml`, you'll usually define health checks under `http_service.checks` (or the alternative `services.http_checks` if you're not using the http_service shortcut) for HTTP-based apps. These checks are simple: make a request to a path like `/`, expect a `200 OK` response, and fail if you get a redirect or timeout. If your app redirects HTTP to HTTPS, add a header like `X-Forwarded-Proto = "https"` to avoid false failures.
+
+The most common health check failure modes are easy to trip over:
+
+- getting 301/302s instead of 200 responses
+- hitting a path that loads slowly under cold start conditions
+- forgetting to delay checks with a `grace_period` after boot
+
+A typical HTTP health check might look like this:
+
+```
+[http_service.checks]
+ interval = "15s"
+ timeout = "2s"
+ grace_period = "10s"
+ method = "GET"
+ path = "/healthz"
+ headers = { X-Forwarded-Proto = "https" }
+```
+
+For apps that aren't serving HTTP, or where you want lighter checks, [TCP health checks](/docs/reference/health-checks/#service-level-checks) exist too. These just attempt to open a socket to the port you've configured; if the connection is accepted, the Machine is healthy.
+
+[Top-level health checks](/docs/reference/health-checks/#top-level-checks)—defined in the `[checks]` section of your `fly.toml`—are for observability, not traffic control. They don't influence routing; if a Machine fails one, we won’t pull it out of rotation. That makes them ideal for internal monitoring and alerting, especially for background workers or other non-public-facing services where you still want to know if something’s gone sideways, but don’t need Fly to reroute traffic automatically.
+
+Deployments get another layer: `machine_checks`. These aren't about routing traffic; they're about catching bad deploys before they go live. A `machine_check` boots a throwaway Machine, sets `FLY_TEST_MACHINE_IP`, and lets you poke it however you want. You can run integration tests, perform simulated traffic, check database access, or validate config. Fail the check, and the deploy halts. This is perfect for canary testing new releases.
+
+**Example:**
+
+```
+[services.machine_checks]
+ command = ["/bin/bash", "-c", "curl -f http://$FLY_TEST_MACHINE_IP:8080/healthz"]
+ interval = "30s"
+ timeout = "5s"
+```
+
+Note: `machine_checks` run in a temporary Machine with the new image before it's deployed anywhere else. They're isolated, so they won't affect your running app—but they can save you from pushing broken code into production.
+
+If a Machine fails a health check, Fly Proxy pulls it out of the rotation. That Machine can still be running, logging, and debugging, but it's invisible to your users.
+
+## Deployment strategies: choose your own trade-offs
+
+Working with health checks and at least two Machines, you can avoid downtime. The platform won't kill a healthy Machine until a new one is up and running.
+
+Fly.io supports a few deployment strategies in the `[deploy]` section of `fly.toml`:
+
+- **Rolling (default):** Replace Machines one at a time. Use `max_unavailable` to control how many go down at once (e.g., `1` or `25%`).
+- **Immediate:** Burn the ships. All Machines update at once, no health checks, hope for the best.
+- **Canary:** Start with one Machine. If it's healthy, continue with rolling. This can't be used with attached volumes.
+- **Bluegreen:** Boot new Machines alongside old ones. Only switch traffic after all new Machines pass checks. Fastest, safest, but also can't use attached volumes.
+
+Every deploy also includes a smoke check: Fly watches Machines for \~10 seconds after they start. If they crash repeatedly, the deploy fails.
+
+Don't forget to set `wait_timeout` if your image is big or startup is slow. It's easy to hit timeouts before Machines even start.
+
+## One-off tasks with `release_command`
+
+Sometimes you need to run a script before your app is actually deployed, like a database migration or other one-off prep work. The `release_command` in your `fly.toml` lets you do exactly that. It spins up a fresh temporary Machine, runs your command, then shuts it down and destroys it. It doesn’t join your deployed Machines.
+
+This runs once per deploy attempt, using the built image and your app’s environment. It won’t have volumes attached, so it’s not for anything that needs persistent state.
+
+If `release_command` fails, the deploy won’t proceed. This is usually what you want. If the migration didn’t work, it's better to stop there.
+
+We’ve got more details, including how `ENTRYPOINT` and `CMD` are handled, in the [reference for `fly.toml`](/docs/reference/configuration/#run-one-off-commands-before-releasing-a-deployment).
+
+## Zero-downtime is a shared responsibility
+
+The Fly.io platform will keep your Machines healthy and traffic flowing. But there's one thing it can't fix: application incompatibility during deploys.
+
+Say you're changing your database schema. You run the migration in a `release_command`, then your Machines update one by one. If the old app code can't handle the new schema, users will see errors while old Machines are still running. Same goes for bluegreen deployments: the new Machines are healthy, but traffic doesn't switch until _all_ of them are.
+
+Fixing this is on you. You need to design schema and app changes that can coexist briefly. That usually means:
+
+- App code that works with both the old and new database schemas
+- Migrations that add columns or tables but don't drop or rename until later
+
+Every framework has guidance for this. Read it. Follow it. Then health checks, deployment strategies, and the platform can do their jobs.
+
+Deploying without downtime isn't automatic. But with a little help from your health checks and some care in your app logic, it's well within reach.
+
+## Related reading
+
+- [Health Checks](/docs/reference/health-checks/)
+- [Deploy an App](/docs/launch/deploy/)
+- [App Configuration (`fly.toml`)](https://fly.io/docs/reference/configuration/)
\ No newline at end of file
diff --git a/blueprints/setting-concurrency-limits.html.md b/blueprints/setting-concurrency-limits.html.md
new file mode 100644
index 0000000000..4f895904f3
--- /dev/null
+++ b/blueprints/setting-concurrency-limits.html.md
@@ -0,0 +1,48 @@
+---
+title: Setting Hard and Soft Concurrency Limits on Fly.io
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-05-15
+---
+
+
+
+
+
+## Overview
+
+On Fly.io, machines aren’t created automatically based on traffic; you create them manually. But once a machine is created, it can be stopped when idle (meaning you’re not paying for it), and started again instantly when load picks up. Our “autostart/autostop” feature can drive this process for you — but it only works well if you’ve tuned `soft_limit` and `hard_limit` correctly. Those limits tell Fly when a machine is too busy and it's time to bring more machines online.
+
+## How to tune concurrency limits
+
+Soft and hard limits for Fly Machines don’t have to be a black art. Here’s a quick rundown:
+
+- **hard_limit**: max concurrent requests the machine will ever handle.
+- **soft_limit**: how many it can handle _comfortably_, before Fly begins starting more machines. Incoming requests are distributed on a round-robin fashion to started machines.
+
+The best way to dial this in is to actually test it. Based on the target app you’re working to scale, create a separate Fly app copy with the same machine config. Now set both limits absurdly high (say, 1000), and hammer it with load using something like `wrk`, `hey`, `ab`, `Locust`, or `k6`. Watch CPU, memory, and latency.
+
+What you’re looking for is the point where responses start to slow down below your comfort threshold. That’s your hard limit—just below that threshold. Your comfort level is up to you; a real-time application will want to keep responses under 100ms, but an ordinary CRUD application might be just fine with request times around 250ms.
+
+Now set your soft limit. The soft limit is there to give our proxy time to bring another machine online before your users notice lag. Example: if things start dragging at 20 concurrent requests, set `hard_limit`: 18 and `soft_limit`: 12.
+
+Once you’ve got your limits dialed in, turning on `auto_start_machines` and `auto_stop_machines` gives you hands-off scaling behavior that still respects the boundaries you’ve set. Your app shouldn’t need babysitting to handle spikes, and you won’t end up running more machines than you meant to. It’s a good balance between elasticity and control.
+
+## Capacity Planning: A Real Example
+
+Let’s say:
+
+- Each request takes ~100ms to serve.
+- You’re expecting 750 requests per second.
+- One machine handles about 10 concurrent requests before slowing down.
+
+That means you need 75 concurrent request slots to keep up. So: 750 rps * 100ms = 75 concurrent requests. At 10 per machine, you’ll need 8 machines.
+
+In order to serve requests as they arrive (that is, dispatch 750 requests/second) with an average processing time per request of 100ms, 75 “workers”, or concurrent requests handled, are needed. Based on the above machine capacity of 10 requests, 8 machines should be able to handle the proposed load.
+
+## Related reading
+
+- [Guidelines for Concurrency Settings](/docs/apps/concurrency/) Read more in this reference for the `soft_limit`, `hard_limit`, and `type` settings you’ll use in your `fly.toml`.
+- [App Configuration: `http_service.concurrency`](/docs/reference/configuration/#http_service-concurrency) Check out this section of the `fly.toml` config reference explaining concurrency parameters.
+- [Load Balancing on Fly.io](/docs/reference/load-balancing/) Find out how your concurrency limits affect how the Fly Proxy routes traffic across machines—and why your soft/hard limits matter.
\ No newline at end of file
diff --git a/blueprints/shared-nothing.html.markerb b/blueprints/shared-nothing.html.markerb
index 7028067225..c4b192029b 100644
--- a/blueprints/shared-nothing.html.markerb
+++ b/blueprints/shared-nothing.html.markerb
@@ -8,35 +8,23 @@ artist: Annie Ruygt
artist_link: https://annieruygtillustration.com/
alt: A number of couples dancing, including a two clones dancing with separate partners.
layout: docs
-nav: firecracker
+nav: guides
redirect_from: "/blog/shared-nothing/"
date: 2024-03-05
author: rubys
---
-
-
-
- Image by
-
-
-
-
-
-
-
-
- Annie Ruygt
-
-
-
+
+
+
+
+
-I’m Sam Ruby, and my day job is to make Fly.io better for Rails and JS. (Go try us out!) But today I’m talking about an app I wrote in my personal capacity as a ballroom dance nerd. This article is about how a serverful, stateful, shared-nothing architecture hit the spot for me when it came time to scale to multiple customers in different parts of the world.
+This guide from Rails and JS expert Sam Ruby is based on his personal capacity as a ballroom dance nerd. It covers how a serverful, stateful, shared-nothing architecture hit the spot for him when it came time to scale to multiple customers in different parts of the world.
-
-
+## Introduction
Once upon a time, I found myself double booked at a ballroom dance competition. Read that [story in more detail on my blog](http://intertwingly.net/blog/2022/08/13/Unretiring), but essentially: you’re only supposed to get one shot to dance in a given heat, and I ended up listed twice in the spreadsheet for that heat. I decided to write an app to take the load of manual entry and scheduling off of organizers, and so that no one would ever be asked to do something impossible and be at two places at one time.
@@ -79,7 +67,7 @@ An excerpt from DHH's [Majestic Monolith](https://signalvnoise.com/svn3/the-maje
> Every time you extract a collaboration between objects to a collaboration between systems, you’re accepting a world of hurt with a myriad of liabilities and failure states. What to do when services are down, how to migrate in concert, and all the pain of running many services in the first place.
-Another way to look at this is: as needs grow, do you really want to share a database and redis instances between customers? (Or, in this case, between events?)
+Another way to look at this is: as needs grow, do you really want to share a database and Redis instances between customers? (Or, in this case, between events?)
If "no" turns out to be a viable answer: go with it. Put all of the services needed to support one customer in one Docker image and then repeat that set as often
as needed. Eliminating the need for a network in producing a response to a request both increases throughput and reliability.
@@ -94,7 +82,7 @@ and grows to 450MB when active. That being said, I have seen machines fail due
each machine 1GB. I also define an additional 2G of [swap](https://fly.io/docs/reference/configuration/#swap_size_mb-option) as
I would rather the machine run slower under peak load than crash.
-Finally, invest the time to set up a [wireguard network](https://fly.io/docs/networking/private-networking/) that lets you VPN into your own private network of machines.
+Finally, invest the time to set up a [WireGuard network](https://fly.io/docs/networking/private-networking/) that lets you VPN into your own private network of machines.
## Multi-tenancy
@@ -127,7 +115,7 @@ still listed as todo, but the following is what I have been able to figure out:
- One action cable process is allocated per server (i.e., listen port).
- In order to share the action cable process, all apps on the same server will
- need to share the same redis URL and channel prefix. The [Rails
+ need to share the same Redis URL and channel prefix. The [Rails
documentation](https://guides.rubyonrails.org/action_cable_overview.html#redis-adapter)
suggests that you use a different channel prefix for different applications
on the same server -- **IGNORE THAT**.
@@ -460,3 +448,9 @@ But first, some useful approximations that characterize this app, many of which
* Transactions are generally a blend of reads and writes with neither dominating.
This lends itself well to horizontal scaling, where every event effectively has a dedicated VM. Again, this is a useful approximation - as not every event is "active", multiple events can share a VM. As needs change, databases can be moved to different VMs.
+
+## Related reading
+
+- [Getting Started with N‑Tier Architecture](/docs/blueprints/n-tier-architecture/) A contrasting architecture pattern where state is shared across the data tier — useful for understanding when shared‑nothing *isn’t* the best fit.
+- [Session Affinity (a.k.a. Sticky Sessions)](/docs/blueprints/sticky-sessions/) Focused on routing and state‑isolation patterns that often come up when you adopt a shared‑nothing approach and need to keep state local to a machine.
+- [Networking](/docs/networking/) The comprehensive networking overview (private networks, Anycast, WireGuard tunnels, routing) — helpful when building globally distributed isolated nodes in a shared‐nothing architecture.
\ No newline at end of file
diff --git a/blueprints/staging-prod-isolation.html.md b/blueprints/staging-prod-isolation.html.md
index 03d46dd185..982d2658c3 100644
--- a/blueprints/staging-prod-isolation.html.md
+++ b/blueprints/staging-prod-isolation.html.md
@@ -1,10 +1,16 @@
---
title: Staging and production isolation
layout: docs
-nav: firecracker
+nav: guides
redirect_from: /docs/going-to-production/the-basics/production-staging-isolation/
---
+
+
+
+
+## Overview
+
You want to isolate your production environment from your test or staging environments by limiting production access to the smallest possible group. You can get dependable isolation by using multiple organizations. Organization access is invitation-only and orgs are isolated from one another at a low level by our private networking architecture: every organization has its own [private network](/docs/networking/private-networking/) by default.
All Fly.io accounts start with a "personal" organization. You can create as many organizations as you need, not just for different environments, but also for different projects or clients.
@@ -41,3 +47,8 @@ When you have more than one org, flyctl prompts you to choose an organization wh
- When you only need app isolation within an org, you can use [custom private networks](/docs/networking/custom-private-networks/) to isolate apps from one another by creating an app with `fly apps create` and the `--network` option.
- When you want user or 3rd-party access control, you can use [deploy](https://community.fly.io/t/deploy-tokens/11895) and [org-scoped tokens](https://community.fly.io/t/org-scoped-tokens/13194) to limit access to apps or orgs.
+
+## Related reading
+
+- [Going to production checklist](/docs/apps/going-to-production/) A broad checklist for production‑readiness that includes isolating environments and access control.
+- [Staging environments with GitHub actions](/docs/django/advanced-guides/staging-environments-with-github-actions/) A practical guide focused on using GitHub Actions to deploy dedicated staging environments. Complements the isolation pattern nicely.
\ No newline at end of file
diff --git a/blueprints/sticky-sessions.html.md b/blueprints/sticky-sessions.html.md
index 93479756da..770a8bbfe9 100644
--- a/blueprints/sticky-sessions.html.md
+++ b/blueprints/sticky-sessions.html.md
@@ -1,23 +1,26 @@
---
title: Session Affinity (a.k.a. Sticky Sessions)
layout: docs
-nav: firecracker
+nav: guides
author: rubys
date: 2024-07-17
---
-In a number of scenarios, it is important to ensure that certain requests are
-routed to a specific Machine. This frequently is expressed in the form of
-wanting an entire user's session to be processed by the same Machine.
+
+
+
-There are two basic approaches to addressing this with Fly.io:
- * [fly-force-instance-id](https://fly.io/docs/networking/dynamic-request-routing#the-fly-force-instance-id-request-header) request header
- * [fly-replay](https://fly.io/docs/networking/dynamic-request-routing#the-fly-replay-response-header) response header.
+## Overview
-## Fly-Force-Instance_Id
+In a number of scenarios, it is important to ensure that certain requests are routed to a specific Machine. This frequently is expressed in the form of wanting an entire user's session to be processed by the same Machine.
-This is the preferred approach, but it does require you to have control over the client, typically a browser. The example below uses Rails' Hotwire [Turbo](https://turbo.hotwired.dev/) with [Stimulus](https://stimulus.hotwired.dev/) to
-send the required header.
+There are two approaches to addressing this with Fly.io:
+ * [fly-force-instance-id](#fly-force-instance-id) - Client-side request header
+ * [fly-replay](#fly-replay) - Server-side response header
+
+## Fly-Force-Instance-Id
+
+This approach requires you to have control over the client, typically a browser, but allows for immediate routing without an additional hop. The example below uses Rails' Hotwire [Turbo](https://turbo.hotwired.dev/) with [Stimulus](https://stimulus.hotwired.dev/) to send the required header.
For this to work, the client needs some way of knowing what Machine to route requests to. This can be accomplished by adding attributes to the `` tag in HTML responses. With Rails, those tags would be found in
`app/views/layouts/application.html.erb`.
@@ -64,12 +67,11 @@ This code will subscribe and unsubscribe from
## Fly-Replay
-This approach can be implemented entirely on the server. This will require an additional "hop" to route requests and [is limited to payloads of 1 megabyte](https://fly.io/docs/networking/dynamic-request-routing/#limitations).
+This approach is implemented entirely on the server. Your application examines each request, determines which Machine should handle it, and returns a `fly-replay` response header to route the request accordingly. Initial requests will require an additional "hop" to route, and [requests are limited to payloads of 1 megabyte](https://fly.io/docs/networking/dynamic-request-routing/#limitations).
-The example below creates [Express Middleware](https://expressjs.com/en/guide/using-middleware.html) that creates cookies containing the desired Machine id, and then replays requests that arrive at the wrong destination:
+The example below creates [Express Middleware](https://expressjs.com/en/guide/using-middleware.html) that routes requests based on a session cookie:
```js
-
// Import required modules
import express from "express";
import cookieParser from "cookie-parser";
@@ -80,28 +82,36 @@ const app = express();
// Middleware to parse cookies
app.use(cookieParser());
-// Make sessions sticky
app.use((request, response, next) => {
- if (!process.env.FLY_MACHINE_ID) {
+ // This assumes your application has already established a session using a session_id cookie via your session middleware.
+ const sessionId = request.cookies["session_id"];
+
+ if (!sessionId) {
+ // No session - let any machine handle it
next();
- } else if (!request.cookies["fly-machine-id"]) {
- const maxAge = 6 * 24 * 60 * 60 * 1000; // six days
- response.cookie("fly-machine-id", process.env.FLY_MACHINE_ID, { maxAge });
+ return;
+ }
+
+ // Determine which machine should handle this session
+ // This could be based on:
+ // - Database lookup (session -> machine mapping)
+ // - Consistent hashing of session ID
+ // - Regional routing logic
+ // - Load balancing algorithm
+ // You'll need to implement determineTargetMachine() based on your routing strategy
+ const targetMachineId = determineTargetMachine(sessionId);
+
+ if (targetMachineId === process.env.FLY_MACHINE_ID) {
+ // This is the correct machine - handle the request
next();
- } else if (request.cookies["fly-machine-id"] !== process.env.FLY_MACHINE_ID) {
- response.set('Fly-Replay', `instance=${request.cookies["fly-machine-id"]}`)
- response.status(307)
- response.send()
} else {
- next();
+ // Route to the correct machine
+ response.set('Fly-Replay', `instance=${targetMachineId}`);
+ response.status(307);
+ response.send();
}
});
-// For demonstration purposes: show the Machine id that produced the response
-app.get("/", (_request, response) => {
- response.send(`FLY_MACHINE_ID: ${JSON.stringify(process.env.FLY_MACHINE_ID)}`);
-});
-
// Start the Express server
const port = 3000;
app.listen(port, () => {
@@ -109,13 +119,38 @@ app.listen(port, () => {
});
```
-The key code here appears directly after the `// Make sessions sticky` comment.
+### Optimizing with Replay Caching
+
+For production workloads, having your application replay every request can create unnecessary load and latency. You can configure Fly Proxy to cache replay decisions, so only the first request in a session needs to consult your application.
+
+Add replay cache rules to your `fly.toml`:
+
+```toml
+[http_service]
+ internal_port = 8080
+ force_https = true
+
+ [[http_service.http_options.replay_cache]]
+ path_prefix = "/"
+ ttl_seconds = 300
+ type = "cookie"
+ name = "session_id"
+```
+
+With this configuration:
+1. The first request with a given `session_id` hits your app
+2. Your app returns a `fly-replay` response
+3. Fly Proxy caches this decision for that specific `session_id` value
+4. For the next 5 minutes, requests with the same `session_id` are automatically routed without consulting your app
+
+Caching is an optimization, not a guarantee, so your origin app still needs to handle replaying requests that aren't routed by a cached replay.
-If the `FLY_MACHINE_ID` environment variable is not set, this code is not running on a Fly.io Machine, and this middleware immediately exits.
+Replays can be cached on any existing cookie or request header. For sticky sessions with an API, for example, you might choose to cache based on the `Authorization` header.
-If the `fly-machine-id` cookie is not set, then this presumably is the first request of a session, and the cookie is
-set to ensure that future requests are routed to the same destination.
+For complete details on replay caching configuration, see [Session-based Replay Caching](/docs/networking/dynamic-request-routing/#session-based-replay-caching).
-If the cookie does not match the environment variable, then a response containing a `Fly-Replay` header is created, which triggers the Fly Proxy to replay the request to the desired Machine.
+## Related reading
-If none of these conditions is satisfied, the middleware passes on the request to the next handler.
+- [Dynamic Request Routing with `fly‑replay`](/docs/networking/dynamic-request-routing/) Explains how `fly‑replay` lets you route requests to a specific machine or region—core technique for sticky sessions.
+- [Load Balancing](/docs/reference/load-balancing/) Details how the Fly Proxy routes traffic among Machines, which underpins how session‑affinity decisions get made.
+- [Networking](/docs/networking/) A broad overview of how Fly handles public/private networking, Anycast IPs, WireGuard mesh, domain routing, and more.
\ No newline at end of file
diff --git a/blueprints/supercronic.html.md b/blueprints/supercronic.html.md
index 24c725415e..bd33e9258a 100644
--- a/blueprints/supercronic.html.md
+++ b/blueprints/supercronic.html.md
@@ -1,7 +1,7 @@
---
title: Crontab with Supercronic
layout: docs
-nav: firecracker
+nav: guides
author: brad
categories:
- cron
@@ -11,6 +11,12 @@ redirect_from:
- /docs/app-guides/supercronic/
---
+
+
+
+
+## Overview
+
`crontab` is a little too opinionated for containers—it's a great little tool, but when run inside of containers it doesn't grab `ENV` vars how we'd like it to. Fortunately there's a Go binary called `supercronic` that's a drop-in replacement for containers.
The good news is that it's pretty easy to get it running on Fly with a little bit of copy and pasting. Let's get to it.
@@ -91,7 +97,13 @@ $ fly scale count cron=1 web=
That's it! If all went well you now have cron running in a `cron` process in a Fly virtual machine. When you `fly deploy` it will get the latest code changes and reboot the virtual machines.
-# Resources
+## Resources
- [https://github.com/fly-apps/supercronic](https://github.com/fly-apps/supercronic+external)
- [https://cron.help](https://cron.help+external)
+
+## Related Reading
+
+- [Task scheduling guide with Cron Manager and friends](/docs/blueprints/task-scheduling/) A broader look at scheduling jobs on Fly.io — covers Cron Manager, Supercronic, scheduled Machines, and more.
+- [Run multiple process groups in an app](/docs/launch/processes/) Explains how to define processes in `fly.toml`, run multiple programs (web, cron, workers), scale by group.
+- [App Configuration (`fly.toml`)](/docs/reference/configuration/) In‑depth look at the config file that orchestrates your services, processes, health checks, etc.
\ No newline at end of file
diff --git a/blueprints/task-scheduling.html.md b/blueprints/task-scheduling.html.md
new file mode 100644
index 0000000000..b5755ddd44
--- /dev/null
+++ b/blueprints/task-scheduling.html.md
@@ -0,0 +1,149 @@
+---
+title: Task scheduling guide with Cron Manager and friends
+layout: docs
+nav: guides
+author: kcmartin
+categories:
+ - cron
+date: 2025-07-18
+---
+
+
+
+
+
+## Overview
+
+Running on a schedule: it's not just for trains. If your app needs to rebuild a cache every night, prune old data, or email a weekly newsletter to your users, you’re probably looking for a way to run a cron job. Fly.io gives you a few ways to get that done.
+
+We’ll walk through options for task scheduling, starting with the most robust and ending with the “it works, okay?” tier.
+
+## Cron Manager
+
+[Cron Manager](https://github.com/fly-apps/cron-manager) is our “batteries-included” solution for running scheduled jobs. It’s a small Fly app that spins up temporary Machines—one per job—and tears them down afterward. Think of it as a very polite butler that rings a bell and quietly disappears before you remember you don’t have a butler.
+
+### How It Works
+
+You deploy Cron Manager as its own Fly app. It stays online, watching a `schedules.json` file. When a job’s scheduled time hits, it boots a one-off Machine with whatever image, region, and resources you specify. That Machine runs the command, exits, and disappears like a good container should.
+
+Jobs run in complete isolation—no shared environment, no weird state carried over from the last job. And because they’re just Fly Machines, you get all the perks: region placement, resource control, image selection, and clean logs.
+
+### Why Use This
+
+- **Isolation**: Each job gets its own Machine. Nothing leaks.
+- **Central config**: `schedules.json` lives in Git. Changes are versioned, auditable, and deployable.
+- **Easy updates**: Want to change a command or schedule? Update the file and redeploy.
+- **Job logs**: Each Machine has its own logs and lifecycle. Debugging isn’t a treasure hunt.
+
+### Setup
+
+1. Clone [cron-manager](https://github.com/fly-apps/cron-manager)
+1. Create a new Fly app and deploy it with `fly deploy`
+1. Set your `FLY_API_TOKEN` as a secret for the app
+1. Define your job schedules in `schedules.json`
+
+Here’s a sample job:
+
+```json
+{
+ "name": "daily-cache-rebuild",
+ "app_name": "my-app",
+ "schedule": "0 3 * * *",
+ "region": "lhr",
+ "command": ["bin/rebuild-cache"],
+ "command_timeout": 300,
+ "enabled": true,
+ "config": {
+ "image": "my-app-image",
+ "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 },
+ "restart": { "policy": "no" }
+ }
+}
+```
+
+### Managing Jobs
+
+Once deployed, you can:
+
+- List jobs: `cm schedules list`
+- Check history: `cm jobs list `
+- Peek at details: `cm jobs show `
+- Trigger one manually: `cm jobs trigger `
+
+SSH into the Cron Manager VM if you need to do some spelunking.
+
+---
+
+## Supercronic
+
+Sometimes you don’t need a full orchestration layer—you just want a crontab to run inside your app container. Supercronic does that. It’s a container-friendly cron runner that handles environment variables correctly.
+
+### How It Works
+
+[Supercronic](/docs/blueprints/supercronic/) runs as a background process inside your app. You add a `crontab` file, install Supercronic in your Dockerfile, and define a `cron` process in `fly.toml`. Done.
+
+Well, almost done—remember to scale it correctly.
+
+### Setup
+
+1. Add a crontab file (`/app/crontab`) using standard syntax
+1. Install Supercronic in your Dockerfile
+1. In `fly.toml`:
+
+```
+[processes]
+app = "bin/server"
+cron = "supercronic /app/crontab"
+```
+
+1. Scale it:
+
+```bash
+fly scale count app=2 cron=1
+```
+
+Only run one copy of the cron process. Unless your goal is to send four copies of the same reminder email. (In which case, carry on.)
+
+---
+
+## Scheduled Machines
+
+Fly Machines support basic scheduling out of the box: you can tell a Machine to start hourly, daily, weekly, or monthly. The API is simple. The downside? You don’t get fine-grained control—just interval buckets.
+
+This is fine for non-critical cleanup tasks or anything where “roughly once a day” is good enough.
+
+---
+
+## In-App Scheduling
+
+If you’re already using a scheduler like `apscheduler` in a Python app—or you’ve duct-taped one together in Node—you can use it to call `fly machine run --rm` and spin up ephemeral Machines manually.
+
+This gives you full control and is technically elegant, though it does require some careful footwork. It’s the “build your own trigger system” option, best for when you’re already halfway there.
+
+---
+
+## Additional Options
+
+In addition to the options above, you can use an external scheduling service, like [GitHub Actions schedule](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule), or [easycron](https://www.easycron.com/), an online cron service which can send out API requests to wake machines up.
+
+---
+
+## Summary
+
+| Option | Use When… | Good To Know |
+| --- | --- | --- |
+| **Cron Manager** | You want isolated, auditable job runs | Requires a separate app |
+| **Supercronic** | You want quick cron jobs inside your container | Keep the process count to one |
+| **Scheduled Machines** | You want simple, low-frequency jobs (~1x/day) | Not for precise timing |
+| **In-App Scheduler** | You want full control and don't mind plumbing | Write your own orchestration |
+
+If you're not sure where to start, Cron Manager is the most production-hardened option. But Supercronic gets you 80% of the way with almost no setup, and sometimes that’s all you need.
+
+---
+
+## Related Reading
+
+- [Cron Manager](https://github.com/fly-apps/cron-manager)
+- [Crontab with Supercronic](/docs/blueprints/supercronic/)
+- [Deferring long-running tasks to a work queue](/docs/blueprints/work-queues/)
+
diff --git a/blueprints/using-base-images-for-faster-deployments.html.md b/blueprints/using-base-images-for-faster-deployments.html.md
new file mode 100644
index 0000000000..08b3e6096d
--- /dev/null
+++ b/blueprints/using-base-images-for-faster-deployments.html.md
@@ -0,0 +1,272 @@
+---
+title: Using base images for faster deployments
+layout: docs
+nav: guides
+---
+
+
+
+
+
+## Overview
+
+This guide explains how to use base images for faster deployments. A base image is any image that is intended to be used as the `FROM` line in a Dockerfile. Each app that is deployed on Fly.io can add an image to the Fly registry.
+
+Every app in the same organization can access the image for any other app and use it as the `FROM` line in their Dockerfile. This means that it is very easy to make a base image by making a second app to use as the base image.
+
+## Why use a base image?
+
+Most developers have a specific reason for using a base image in their project. Usually it's one of the following:
+
+* The base image can be built with all of their app's dependencies. Rebuilding the app on top of this base image means the deploy process is only running the app-specific build processes, not the entire dependency tree.
+* The same app is deployed for a number of different end users. Each end user has some specific files or configurations to build into the image. A base image can get *most of the way* to the final configuration, and their app can do the final steps.
+
+
+## How to make a base image
+
+This guide walks through building and deploying an example application called go-fly-a-site, which runs a simple Go web server. After deploying the app, you'll create a base image from its non-app-specific parts. Finally, you'll update the app to use that base image.
+
+### Making the app
+
+Our program will be very simple. It will respond to HTTP requests on port 8080 and respond with "Hello World!".
+
+```GO
+package main
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func main() {
+ http.HandleFunc("/", HelloServer)
+ http.ListenAndServe(":8080", nil)
+}
+
+func HelloServer(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, "Hello, world!\r\n")
+}
+
+```
+
+Create a Dockerfile based on Debian 12. Install curl and go, compile the Go code, and configure the image to run the compiled binary.
+
+```Dockerfile
+FROM debian:12
+
+# Install Go
+RUN apt-get update; apt-get upgrade; apt-get -y install golang
+
+# Copy the code for our server to /opt/main.go
+COPY main.go /opt/main.go
+
+# Build /opt/server from the code in /opt/main.go
+RUN go build -o /opt/server /opt/main.go
+
+# Run our code from main.go as /opt/server
+CMD [ "/opt/server" ]
+```
+
+With `main.go` and the `Dockerfile` in your project directory, deploy the application:
+
+```bash
+$ fly launch --name go-fly-a-site --vm-size shared-cpu-1x --no-deploy
+....
+Your app is ready! Deploy with `flyctl deploy`
+```
+
+Now we have an additional file, `fly.toml` and have created the app `go-fly-a-site`.
+
+Deploy the app:
+
+```bash
+$ fly deploy
+==> Verifying app config
+Validating /Users/fly/Code/go-fly-a-site/fly.toml
+✓ Configuration is valid
+--> Verified app config
+==> Building image
+==> Building image with Depot
+--> build:
+...
+
+Visit your newly deployed app at https://go-fly-a-site.fly.dev/
+
+```
+
+We can see it's running:
+
+```bash
+$ curl https://go-fly-a-site.fly.dev/
+Hello, world!
+```
+
+Now our app is built and running. This is great, but each time we `fly deploy` we might be reinstalling Go through apt. We probably want to do that much less often than we want to update our `main.go`, so we can make a base image that installs go for us.
+
+### Make a base image
+
+Let's make a new file called `base.Dockerfile`. For this file, we will keep the Go install. We will replace the previous `CMD` statement with `[ "sleep", "inf" ]` so that we can easily SSH into this image to inspect it if needed in the future.
+
+
+```Dockerfile
+FROM debian:12
+
+# Install Go
+RUN apt-get update; apt-get upgrade; apt-get -y install golang
+
+CMD [ "sleep", "inf" ]
+```
+Copy `fly.toml` to `fly-base.toml` and make the following modification:
+
+* Append -base to the app name.
+* Explicitly include dockerfile in the `[build]` section.
+* Remove the `[http_service]` section.
+
+The resulting file looks like this:
+
+```toml
+app = 'go-fly-a-site-base'
+primary_region = 'lax'
+
+[build]
+ dockerfile = "base.Dockerfile"
+
+[[vm]]
+ size = 'shared-cpu-1x'
+```
+
+Create the app using the following options:
+
+* `--ha=false` ensures the app uses a single Machine.
+* `--config base.fly.toml` specifies the correct config file.
+
+When prompted about the existing `fly.toml` file, select yes to copy its configuration. You can skip tweaking the settings.
+
+```bash
+$ fly launch --ha=false --config base.fly.toml
+An existing fly.toml file was found for app go-fly-a-site-base
+? Would you like to copy its configuration to the new app? Yes
+Using build strategies '[the "base.Dockerfile" dockerfile]'. Remove [build] from fly.toml to force a rescan
+Creating app in /Users/fly/Code/go-fly-a-site
+We're about to launch your app on Fly.io. Here's what you're getting:
+
+...
+Region: Los Angeles, California (US) (from your fly.toml)
+App Machines: shared-cpu-1x, 256MB RAM (from your fly.toml)
+...
+
+? Do you want to tweak these settings before proceeding? (y/N)
+Created app 'go-fly-a-site-base' in organization 'personal'
+Admin URL: https://fly.io/apps/go-fly-a-site-base
+Hostname: go-fly-a-site-base.fly.dev
+Wrote config file base.fly.toml
+...
+--> build:
+--> Building image done
+image: registry.fly.io/go-fly-a-site-base:deployment-01JTKF7JXJ1ZGMA76GCJ5WGZQZ
+image size: 233 MB
+```
+
+Some things to note:
+
+* We have the base image name we can use, it's the URL provided in `image:`.
+* When we go to the admin URL, we can click **Registry** and see the same image URL.
+* The **Registry** will have the list of images that have been created.
+* Any app in your organization can use the image URL for a `FROM` statement in a Dockerfile.
+* Apps in any other organization will get an error if they try to use this image.
+
+
+### Updating the app to use the base image
+
+After creating the base image, update the Dockerfile for go-fly-a-site:
+
+
+```Dockerfile
+FROM registry.fly.io/go-fly-a-site-base:deployment-01JTKF7JXJ1ZGMA76GCJ5WGZQZ
+
+# Copy the code for our server to /opt/main.go
+COPY main.go /opt/main.go
+
+# Build /opt/server from the code in /opt/main.go
+RUN go build -o /opt/server /opt/main.go
+
+# Run our code from main.go as /opt/server
+CMD [ "/opt/server" ]
+```
+
+With the updated Dockerfile in place, deploy the app with `fly deploy`.
+
+```bash
+$ fly deploy
+==> Verifying app config
+Validating /Users/fly/Code/go-fly-a-site/fly.toml
+✓ Configuration is valid
+--> Verified app config
+==> Building image
+==> Building image with Depot
+--> build:
+[+] Building 20.7s (8/8) FINISHED
+ ...
+ => [internal] load metadata for registry.fly.io go-fly-a-site-base:deployment-01JTKF7JXJ1ZGMA76GCJ5WGZQZ
+ ...
+ ```
+
+ We can see that it used the new base image, and the application is running:
+
+```bash
+$ curl https://go-fly-a-site.fly.dev/
+Hello, world!
+```
+
+We can shutdown the one Machine for the base image:
+
+```
+$ fly machine list --config base.fly.toml
+1 machines have been retrieved from app go-fly-a-site-base.
+View them in the UI here
+
+go-fly-a-site-base
+ID NAME ...
+d890175f6940e8 wandering-wave-6179 ...
+````
+
+Grab the Machine ID and then run `fly machine stop` with the app and Machine id:
+
+```bash
+$ fly machine stop d890175f6940e8 --config base.fly.toml
+Sending kill signal to machine d890175f6940e8...
+d890175f6940e8 has been successfully stopped
+```
+
+You’ll only be charged for the root file system storage of the image that's being stored.
+
+Now we have a base image for our project! We can modify and deploy our `main.go` code without having to reinstall Go. If we ever need to add additional dependencies to the base image, we just modify the `base.Dockerfile`, deploy it again, and get the new `image:` from the deploy command or in the **Registry** section of the app's dashboard.
+
+## Command Reference
+
+Get Machines for the base image: `fly machine list --config base.fly.toml`
+
+Start Machine for the base image: `fly machine start --config base.fly.toml`
+
+Stop Machine for the base image: `fly machine stop --config base.fly.toml`
+
+Get SSH access to the base image: `fly ssh console --config base.fly.toml` (Make sure the Machine is started)
+
+Rebuild the base image: `fly deploy --config base.fly.toml` (Update `base.Dockerfile` with your changes first, and remember to stop the Machine when you're done)
+
+## Additional Notes
+
+If you have a base image that many different apps will use, it can make sense to keep it all in its own app directory. You can use `fly.toml` and `Dockerfile` like a normal app.
+
+The base image will stick around in the registry and as long as an image is associated with a Machine -- even if the Machine is stopped -- it won't be deleted.
+
+You can find the registry URL of the images for your base image in the dashboard under the **Registry** tab for the app, or by running `fly releases --image`
+
+## Related use case
+
+Want to skip the Fly builder and use Docker tools directly to manage your images? See [Managing Docker Images with Fly.io's Private Registry](https://fly.io/docs/blueprints/using-the-fly-docker-registry/), a guide that walks through building, pushing, deploying, and managing images via Fly.io’s Docker registry!
+
+## Related reading
+
+- [Deploy with a Dockerfile](https://fly.io/docs/languages-and-frameworks/dockerfile/) The general guide on how to deploy apps from Dockerfiles — useful context when you’re building a base image.
+- [App Configuration (`fly.toml`)](https://fly.io/docs/reference/configuration/) Details on how your `fly.toml` orchestrates builds, images, and deployments.
diff --git a/blueprints/using-the-fly-docker-registry.html.md b/blueprints/using-the-fly-docker-registry.html.md
new file mode 100644
index 0000000000..38384d39e6
--- /dev/null
+++ b/blueprints/using-the-fly-docker-registry.html.md
@@ -0,0 +1,129 @@
+---
+title: Managing Docker Images with Fly.io's Private Registry
+layout: docs
+nav: guides
+---
+
+
+
+
+
+## Overview
+
+Fly.io lets you deploy Docker containers as lightweight Firecracker VMs, running globally. While `fly launch` supports many frameworks and auto-generates deployment configurations, you can also build and push your own Docker images to Fly.io’s private registry and deploy them manually.
+
+This guide walks through building, pushing, deploying, and managing images via Fly.io’s Docker registry.
+
+## Tagging images for the Fly.io registry
+
+Each Fly.io app gets a dedicated registry path in the form of:
+
+```
+registry.fly.io/
+```
+
+To create a new app:
+
+```sh
+fly apps create
+```
+
+App names must be globally unique. You can generate a unique one automatically:
+
+```sh
+fly apps create --generate-name
+```
+
+Then tag your image for the app's registry:
+
+```sh
+docker build -t registry.fly.io/: .
+```
+
+## Authenticating and pushing to the registry
+
+Authenticate Docker to use the Fly.io registry:
+
+```sh
+fly auth docker
+```
+
+This command updates your `~/.docker/config.json` with the required auth token for `registry.fly.io`. Once authenticated, push your image:
+
+```sh
+docker push registry.fly.io/:
+```
+
+## Deploying the image
+
+You can deploy images from the Fly.io registry or public registries, depending on where the image is hosted.
+
+### To deploy a private image pushed to Fly.io's registry:
+
+Use the `--image` option with the `fly deploy` command:
+
+```sh
+fly deploy --app --image registry.fly.io/:
+```
+
+### To deploy a public image from Docker Hub or another public registry:
+
+Specify the image in your `fly.toml` file:
+
+```toml
+[build]
+ image = "flyio/hellofly:latest"
+```
+
+## Reusing images across apps
+
+Although registry URLs are structured per app, access is scoped per organization. This means you can push an image to `registry.fly.io/app-1` and use it in another app (e.g. `app-2`) if both apps are in the same Fly.io organization:
+
+```sh
+fly deploy --app app-2 --image registry.fly.io/app-1:tag
+```
+
+This pattern is useful for SaaS platforms that build once and deploy many times.
+
+## Verifying image existence
+
+You can verify that an image exists in the Fly registry without deploying it using Docker:
+
+```sh
+docker manifest inspect registry.fly.io/my-app-name:my-tag
+```
+
+This command returns data if the image exists, or exits with code 1 if it does not. It requires prior authentication using `fly auth docker`.
+
+For most workflows, use the `fly deploy --image` option with the Fly Docker registry. For advanced flows like multi-app SaaS provisioning, track image tags using `docker manifest inspect` or associate them with Machines to retain them.
+
+## Listing image tags
+
+There is no API or GraphQL query to list all image tags in the registry. Tags only become visible to Fly when they are part of a release. You can view deployed image references with:
+
+```sh
+fly releases -a my-app-name --image
+```
+
+If you're using the GraphQL API to query releases, use the following query format:
+
+```json
+{
+ "query": "query($appName: String!, $limit: Int!) { app(name: $appName) { releases: releasesUnprocessed(first: $limit) { nodes { id version description reason status imageRef stable user { id email name } createdAt } } } }",
+ "variables": {
+ "appName": "my-app-name",
+ "limit": 25
+ }
+}
+```
+
+This will only return images used in past deploys.
+
+## Related use case
+
+Want to optimize your deploys with shared layers and pre-installed dependencies? See the [Using base images for faster deployments](https://fly.io/docs/blueprints/using-base-images-for-faster-deployments/#how-to-make-a-base-image+external) blueprint.
+
+## Related reading
+
+- [Deploy with a Dockerfile](https://fly.io/docs/languages-and-frameworks/dockerfile/) The general guide on how to deploy apps from Dockerfiles — useful context when you’re building a base image.
+- [App Configuration (`fly.toml`)](https://fly.io/docs/reference/configuration/) Details on how your `fly.toml` orchestrates builds, images, and deployments.
diff --git a/blueprints/volume-forking.html.md b/blueprints/volume-forking.html.md
new file mode 100644
index 0000000000..9c3c605c7c
--- /dev/null
+++ b/blueprints/volume-forking.html.md
@@ -0,0 +1,130 @@
+---
+title: Using Fly Volume forks for faster startup times
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-09-12
+---
+
+
+
+
+
+
+**This guide shows how to preload large files onto volumes using forking to improve startup performance. This is especially useful for apps with big model files, binaries, or databases.**
+
+
+## Overview
+
+If your app needs large local files like ML models, SQLite databases, or game assets, you're probably copying them into place at boot. Maybe you bake them into your image, or have your app download them on first run. This works, but it has downsides:
+
+- **Slow startup times**: waiting for multi-GB file downloads.
+- **Wasted bandwidth**: each new machine pulls the same data.
+- **Image bloat**: large files increase your OCI/Docker image size.
+
+There's a better way. Write those files to a Fly Volume so they _persist_ after they've been downloaded. Fly Volumes support [forking](docs/volumes/volume-manage/#create-a-copy-of-a-volume-fork-a-volume), a fast, storage-efficient way to create new volumes preloaded with data, skipping downloads and startup delays.
+
+---
+
+## Why use this pattern?
+
+You’re cloning machines and want them to boot fast, with all their large files already in place.
+
+This isn’t about long-term persistence (though volumes can be used for that, too). It’s about getting cold-start performance close to warm-start, by skipping the “download half the internet” phase of your boot process.
+
+It’s especially useful when:
+
+- You’ve already run a setup step once and don’t want to repeat it.
+- Your app reads large files but doesn’t modify them.
+- You're scaling up on demand and startup time matters.
+
+---
+
+## How volume forking works
+
+When you fork a volume:
+
+- **Data is copied from the source volume**, block-for-block.
+- **Forked volumes are usable immediately**, even while they are in the hydrating state, thanks to lazy fetching (a system where data blocks are only fetched from the source volume when your app actually tries to read them).
+
+Your app can start reading files as soon as the fork succeeds. We start background replication right away, but behind the scenes we use a network block device to fetch blocks on demand. So startup is fast, and the data just shows up when your app asks for it.
+
+One tradeoff: disk access might be a little slower while the replication is ongoing. But for many apps, this beats the startup delay of downloading or unpacking files.
+
+---
+
+## Example: cloning a machine with preloaded data
+
+### 1. Make sure the source volume you want to fork is available
+
+```
+fly volumes list -a your-app
+```
+
+Pick the volume with the data you want to reuse.
+
+### 2. Fork the volume
+
+```
+fly volumes fork vol_abc123 -a your-app
+```
+
+You’ll get output like this:
+
+```
+ ID: vol_new_id
+ Name: volume_name
+ App: your-app
+ Region: yul
+ Zone: b5a1
+ Size GB: 20
+ Encrypted: true
+ Created at: 05 Sep 25 21:52 UTC
+```
+
+Note the `vol_new_id`.
+
+### 3. Clone the machine and attach the forked volume
+
+```
+fly machine clone --attach-volume vol_new_id
+```
+
+Now your new machine starts with the same data in place—no need for your app to download or generate it again.
+
+---
+
+## What to know
+
+- **Forks aren’t instant behind the scenes.** You can read from them immediately, but the full copy happens in the background. Disk I/O might be slower until replication finishes.
+- **Data is a snapshot, not a live link.** Changes to the original after forking don’t show up in the copy (and vice versa).
+- **You can fork cross‑region.** The `--region` flag allows you to target another region for the fork. If you skip it, the volume ends up in the same region by default. Cross-region forks are expected to hydrate more slowly, since all data must be replicated over the network between regions.
+- **Forks have storage costs.** As blocks are fetched or written, forked volumes grow. Keep an eye on storage usage costs if you fork a lot.
+
+---
+
+## (Optional) Automate it in CI
+
+If you're scripting deploys or scaling machines dynamically, you can fold this pattern into a CI job or automation script.
+
+Here’s a rough sketch:
+
+```
+# Step 1: Fork the seed volume
+FORKED_VOL=$(fly volumes fork vol_abc123 -a your-app | grep ID | awk '{ print $2 }')
+
+# Step 2: Clone the machine and attach the fork
+fly machine clone --attach-volume $FORKED_VOL
+```
+
+This lets you create and attach preloaded volumes as part of your rollout process which is useful if you're standing up a new VM per job, or if you're scaling inference machines on demand.
+
+This is totally optional. Most folks will do this manually, or only once per release. But if you're already scripting `fly machine` operations, this fits right in.
+
+## Related reading
+
+- [Fly Volumes overview](/docs/volumes/overview/)
+- [Create and manage volumes](/docs/volumes/volume-manage/)
+- [Volume states](/docs/volumes/volume-states/)
+- `fly volumes fork` [reference](/docs/flyctl/volumes-fork/)
+
diff --git a/blueprints/work-queues.html.md b/blueprints/work-queues.html.md
new file mode 100644
index 0000000000..df43944ada
--- /dev/null
+++ b/blueprints/work-queues.html.md
@@ -0,0 +1,181 @@
+---
+title: Deferring long-running tasks to a distributed work queue
+layout: docs
+nav: guides
+author: kcmartin
+date: 2025-07-17
+---
+
+
+
+
+
+## Overview
+
+Some things just take a while. Generating a PDF. Calling a slow third-party API. Running a machine learning model. Sending an email newsletter, and retrying if the mail server flakes out.
+
+Whatever the reason, if your web app is sitting around waiting for that work to finish, you're doing it wrong. Not just in the “inefficient” sense—but in the “your app will time out and confuse users and make debugging miserable” sense.
+
+Instead, you should offload that work to a background worker.
+
+This isn’t a new idea. The pattern’s been around forever, often called a **work queue,** **job queue,** or **task queue.** Here’s the gist:
+
+- The web app gets a request.
+- Instead of doing everything right away, it drops a job onto a queue.
+- A separate process—called a worker—pulls jobs from that queue and handles them in the background.
+
+This means your web handler can respond immediately. It also means your background work can run on different machines, retry on failure, and scale independently from your frontend.
+
+You’ve probably already used something like this without realizing it. Ever sent an email via a task queue? That was a worker. Ever seen a spinner on a dashboard while a report gets generated? There's probably a job queue behind it.
+
+In this guide, we’ll show you how to set this up—using Celery if you're in Python, BullMQ if you're in Node, and Fly Machines if you're allergic to running idle containers.
+
+If you're already comfortable with queues, skip ahead. But if you’ve been stuffing async tasks into HTTP handlers and hoping for the best, this is your exit ramp. Let’s clean it up.
+
+---
+
+## Why use a work queue?
+
+- **Responsiveness**: Return HTTP responses instantly. Let workers take their time.
+- **Scalability**: Scale web servers and workers independently. No need to waste compute on idle async calls.
+- **Retries**: Jobs can fail and retry cleanly, without blocking a request or annoying a user.
+- **Cost efficiency**: With Fly Machines, you only pay for worker time while the job is running.
+- **Persistence**: Jobs don’t care if a user closes their browser tab. Work still gets done.
+
+---
+
+## Redis and Valkey
+
+Most of the tools in this guide use [Redis](https://redis.io/about/) as a message broker—a fast, in-memory data store that's great for short-lived messages and task queues. If you're deploying on Fly.io, we recommend trying [Valkey](https://valkey.io/), a fork of Redis with a more active community and governance model. You can set up a Valkey cluster without needing an Enterprise license. It works the same way but gives you more flexibility and long-term stability. You can deploy either Redis or Valkey yourself or use a managed service like [Upstash Redis](/docs/upstash/redis/).
+
+---
+
+## Option 1: Celery (Python)
+
+[Celery](https://docs.celeryq.dev/en/latest/getting-started/introduction.html) is a full-featured distributed task queue commonly used with Django and Flask.
+
+### How it works
+
+1. Your app enqueues a task to a message broker (Redis or RabbitMQ).
+1. A worker process picks up the task from the queue and executes it.
+1. Task results can optionally be stored (e.g., in Redis or your database).
+
+### Setup
+
+Install the basics:
+
+```bash
+pip install "celery[redis]" django-celery-results
+```
+
+Create a `celery.py`:
+
+```python
+from celery import Celery
+
+app = Celery("my_app")
+app.config_from_object("django.conf:settings", namespace="CELERY")
+app.autodiscover_tasks()
+```
+
+Use `@shared_task` for defining tasks:
+
+```python
+from celery import shared_task
+
+@shared_task(bind=True, autoretry_for=(Exception,), retry_backoff=3, retry_kwargs={"max_retries": 5})
+def crunch_numbers(self, arg):
+ ...
+```
+
+Run your worker:
+
+```bash
+celery -A my_app worker -l info
+```
+
+### Deploying to Fly.io
+
+Use `fly launch` to generate config and add Upstash Redis.
+
+Update `fly.toml`:
+
+```
+[processes]
+app = "gunicorn my_app.wsgi"
+worker = "celery -A my_app worker -l info"
+```
+
+Then:
+
+```bash
+fly deploy
+fly scale count worker=2
+```
+
+**Region tip**: Put your Redis instance and workers in the same region (e.g. `ord`, `fra`) to avoid unnecessary latency and weird race conditions.
+
+---
+
+## Option 2: On-Demand Workers with Fly Machines
+
+You don’t always need a persistent queue and worker pool. For low-frequency or one-off jobs, you can spin up a Fly Machine per task.
+
+### How it works
+
+1. Your app writes a job (params, metadata) to Redis/Valkey.
+1. It calls the Fly Machines API to spin up a temporary worker.
+1. The worker reads job data from Redis, runs it, and writes back the result.
+1. Your app polls Redis to check status and fetch results.
+1. You shut down the machine and clean up.
+
+This is great for jobs that don’t happen often, and it keeps your infrastructure lean. You can reuse your app image or build a dedicated worker image.
+
+Just make sure your job data and results are JSON-serializable.
+
+---
+
+## Option 3: BullMQ (Node.js)
+
+BullMQ is a Redis-backed queue system for Node.js apps. It works similarly to Celery:
+
+- Your app enqueues jobs to Redis/Valkey.
+- Workers pull jobs and execute them.
+- You get features like retries, rate limits, job delays, and concurrency control.
+
+BullMQ plays nicely with TypeScript, has good docs, and integrates well with modern backend frameworks like NestJS.
+
+Provision Redis via Upstash or use `fly redis create`.
+
+Scale workers as needed based on job load and machine size:
+
+```javascript
+const worker = new Worker("my-queue", async job => {
+ // do your work here
+}, { concurrency: 4 });
+```
+
+---
+
+## Other Options
+
+If none of the above tools fit your stack or mental model, there are plenty of alternatives:
+
+- **Sidekiq** (Ruby): The de facto standard for background jobs in Ruby and Rails apps. Redis-backed, battle-tested, and fast.
+- **Oban** (Phoenix) A robust background job processing library for Elixir
+- **RQ** (Python): A simpler, more lightweight job queue than Celery, great for smaller projects or simpler needs.
+
+Most of these integrate with Redis/Valkey, some support more exotic brokers (like Postgres or Kafka), and all of them can be deployed on Fly.io with the right setup.
+
+---
+
+## Final Notes
+
+Whatever you do, don’t handle long-running jobs inside your HTTP request handlers. Use a job queue. Pick the right tool for your stack and use case, and let your web server get back to doing what it’s good at—responding fast and not melting down under load.
+
+
+
+## Related reading
+
+- [Async workers on Fly Machines](https://fly.io/blog/python-async-workers-on-fly-machines/)
+- [Celery and Django for async tasks](https://fly.io/django-beats/celery-async-tasks-on-fly-machines/)
diff --git a/build/assets/manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js b/build/assets/manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/build/assets/manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js.gz b/build/assets/manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js.gz
new file mode 100644
index 0000000000..66160efeab
Binary files /dev/null and b/build/assets/manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js.gz differ
diff --git a/build/assets/manifest.json b/build/assets/manifest.json
new file mode 100644
index 0000000000..959ddc75a3
--- /dev/null
+++ b/build/assets/manifest.json
@@ -0,0 +1 @@
+{"files":{"manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js":{"logical_path":"manifest.js","mtime":"2025-05-15T14:33:13-07:00","size":0,"digest":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","integrity":"sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="}},"assets":{"manifest.js":"manifest-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js"}}
\ No newline at end of file
diff --git a/database-storage-guides.html.md b/database-storage-guides.html.md
index 90b0905ea0..e25d8ca969 100644
--- a/database-storage-guides.html.md
+++ b/database-storage-guides.html.md
@@ -5,73 +5,33 @@ toc: true
nav: firecracker
---
-The solution to persistent data storage is to usually connect your Fly App to a separate database or object store. If you need hardware-local disk storage on your Machines—for example, if your Fly App _is_ a database or if you want to use [LiteFS](/docs/litefs)—then you can use [Fly Volumes](/docs/volumes/).
+
+
+
-## Fly Volumes - Disk storage
-
-The Fly Machines in your app provide ephemeral storage, so you get a blank slate on every startup. For persistent storage on Fly.io, use Fly Volumes. You can attach volumes on an app directly, or run a separate database app with volume storage and connect an app to that.
-
-- **[Fly Volumes](/docs/volumes/):** A Fly Volume is a slice of NVMe disk storage attached to the server that hosts your Machine. Read the [Fly Volumes overview](/docs/volumes/overview/) to find out if volumes are the best solution for your use case.
-
----
-
-## Object storage services
-
-_Object storage service from our extension partners._
-
-- **[Tigris Global Object Storage](/docs/tigris/)** - [Tigris](https://www.tigrisdata.com/+external) is a globally distributed S3-compatible object storage service on Fly.io infrastructure.
-
----
-
-## Managed database services
-_Managed database services from our extension partners._
+## Managed database service
-- **[Upstash for Redis](/docs/upstash/redis/)** - [Redis](https://redis.io/+external) is an in-memory database commonly used for caching. A managed service by [Upstash](https://upstash.com/+external).
+[Fly.io Managed Postgres](/docs/mpg/) is a production-ready Postgres service that handles the hard parts for you: high availability, automatic failover, encrypted backups, monitoring and metrics, seamless scaling, and 24/7 support to keep your data fast, safe, and always online.
----
-
-## Fly.io databases
-
-_These are not managed services; you deploy and manage them yourself as Fly Apps._
+## Key-value stores
-- **[Fly Postgres](/docs/postgres/)** - Our Postgres app provides a [PostgreSQL](https://www.postgresql.org/+external) database with some tools to make it easier to manage yourself. When you deploy an App on Fly.io, we give you the option to launch a Fly Postgres App and attach it to your App.
-
-- **[LiteFS for SQLite](/docs/litefs/)** - [SQLite](https://www.sqlite.org/index.html+external) is a very lightweight file-based database. [LiteFS](/docs/litefs/) is a distributed file system that transparently replicates SQLite databases. You deploy it and you manage it.
+**[Upstash for Redis](/docs/upstash/redis/)** - [Redis](https://redis.io/+external) is an in-memory database commonly used for caching. A managed service by [Upstash](https://upstash.com/+external).
---
-## Other database and storage options
-
-_Examples to help you get started with other popular storage options._
-
-- **[MySQL and MariaDB](/docs/app-guides/mysql-on-fly/)** - [MySQL](https://www.mysql.com/+external) is a popular relational database. [MariaDB](https://mariadb.org/+external) is a community fork of MySQL and is compatible with MySQL.
-
-- **[EdgeDB](/docs/app-guides/edgedb/)** - [EdgeDB](https://www.edgedb.com/+external) is a graph-relational database that runs on top of Postgres.
+## Fly Volumes - Disk storage
-- **[MinIO Object Storage](/docs/app-guides/minio/)** - [MinIO](https://min.io/+external) is software that allows you to self-host S3-compatible storage.
+The Fly Machines in your app provide ephemeral storage, so you get a blank slate on every startup. For hardware-local, persistent storage on Fly.io, use Fly Volumes. You can attach volumes on an app directly, or run a separate database app with volume storage and connect an app to that.
----
+**[Fly Volumes](/docs/volumes/overview):** A Fly Volume is a slice of NVMe disk storage attached to the server that hosts your Machine. Read the [Fly Volumes overview](/docs/volumes/overview/) to find out if volumes are the best solution for your use case.
-## Recommended external providers
+## Object storage service
-_Options for different fully-managed databases or storage solutions for your Fly Apps._
-
-- [Crunchy Bridge Managed Postgres](https://www.crunchydata.com/products/crunchy-bridge+external) (on AWS, Azure, GCP, or Heroku)
-- [Neon Serverless Postgres](https://neon.tech/+external)
-- [PlanetScale Serverless MySQL](https://planetscale.com/+external) ([guide to use with Fly Apps](/docs/app-guides/planetscale/))
-- [MinIO Hosted Object Storage](https://min.io/+external)
-- [Fauna](https://fauna.com/+external) ([guide to use with Fly Apps](/docs/app-guides/fauna/))
+**[Tigris Global Object Storage](/docs/tigris/)** - [Tigris](https://www.tigrisdata.com/+external) is a globally distributed S3-compatible object storage service hosted on Fly.io infrastructure.
---
-## More external providers
-
-_You can connect your Fly Apps to the usual suspects, too._
+## Have a project with special database or storage requirements?
-- [Amazon RDS for PostgreSQL](https://aws.amazon.com/rds/postgresql/+external)
-- [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/products/postgresql/#overview+external)
-- [Digital Ocean Managed Postgres](https://www.digitalocean.com/products/managed-databases-postgresql+external)
-- [Google Cloud SQL for PostgreSQL](https://cloud.google.com/sql/docs/postgres/+external)
-- [Heroku Managed Data Services](https://www.heroku.com/managed-data-services+external)
-- [AWS S3 Object Storage](https://aws.amazon.com/s3/+external)
+If your project calls for something else, most external databases and storage providers work well with Fly.io. You might just need to spend a bit more time wiring up the networking.
diff --git a/deep-dive/application.html.markerb b/deep-dive/application.html.markerb
index 5285433392..3eb912216e 100644
--- a/deep-dive/application.html.markerb
+++ b/deep-dive/application.html.markerb
@@ -5,9 +5,13 @@ nav: demo
order: 4
---
+
+
+
+
Your demo app is running by default on two [Fly Machines](/docs/machines/), our fast-launching VMs.
-Both Machines stop when not in use, and automatically when a new request comes in. [Autostop/autostart](/docs/launch/autostop-autostart/) is entirely configurable. You can chose to suspend instead of stop, configure a minimum number of Machines to keep running, or even decide never to stop Machines at all.
+Both Machines stop when not in use, and start automatically when a new request comes in. [Autostop/autostart](/docs/launch/autostop-autostart/) is entirely configurable. You can chose to suspend instead of stop, configure a minimum number of Machines to keep running, or even decide never to stop Machines at all.
The purpose of two Machines is twofold: redundancy and scalability. If one Machine goes down, the other can continue on. If both are available, when your app has higher traffic, both can be started to handle requests. You can [vertically scale](/launch/scale-machine/) the CPU and RAM on Machines.
diff --git a/deep-dive/postgresql.html.markerb b/deep-dive/postgresql.html.markerb
index 095ece2edd..745cfd3201 100644
--- a/deep-dive/postgresql.html.markerb
+++ b/deep-dive/postgresql.html.markerb
@@ -5,6 +5,10 @@ order: 5
nav: demo
---
+
+
+
+
Fly Postgres is deployed as a separate app, and that app comes initially configured with a single [Fly Postgres](/docs/postgres/) Machine. That's fine for development, but for production you need redundancy and scalability. With a [few commands](/docs/postgres/advanced-guides/high-availability-and-global-replication/) you can create an HA cluster in your primary region and read-only replicas elsewhere.
And there is no lock in here. We have a list of [recommended external providers](/docs/postgres/getting-started/what-you-should-know/#recommended-external-providers), but you're free to host your database literally anywhere.
diff --git a/deep-dive/redis.html.markerb b/deep-dive/redis.html.markerb
index c798a1993b..327558fe9d 100644
--- a/deep-dive/redis.html.markerb
+++ b/deep-dive/redis.html.markerb
@@ -5,6 +5,10 @@ nav: demo
order: 7
---
+
+
+
+
[Upstash Redis](/docs/upstash/redis/) is used by the deep dive demo app for its [pubsub](https://redis.io/docs/latest/commands/?group=pubsub+external) capabilities, but it can do [so much more](https://upstash.com/docs/redis/overall/rediscompatibility+external).
In particular, Redis is useful for caching:
diff --git a/deep-dive/tigris.html.markerb b/deep-dive/tigris.html.markerb
index 3c14d6d7b5..b34e311605 100644
--- a/deep-dive/tigris.html.markerb
+++ b/deep-dive/tigris.html.markerb
@@ -4,6 +4,9 @@ layout: docs
nav: demo
order: 6
---
+
+
+
[Tigris](https://fly.io/docs/tigris/) is a globally distributed object storage service. It essentially requires no configuration, and seamlessly handles multi-regions. If you upload an audio file to a host in Virginia, you can access it from Amsterdam. Even better: subsequent accesses from regions other than the primary region are served locally.
diff --git a/flyctl/cmd/fly.md b/flyctl/cmd/fly.md
index b17951501c..f0c759cc2d 100644
--- a/flyctl/cmd/fly.md
+++ b/flyctl/cmd/fly.md
@@ -28,11 +28,13 @@ fly [flags]
* [litefs-cloud](/docs/flyctl/litefs-cloud/) - LiteFS Cloud management commands
* [logs](/docs/flyctl/logs/) - View app logs
* [machine](/docs/flyctl/machine/) - Manage Fly Machines.
+* [mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+* [mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
* [mysql](/docs/flyctl/mysql/) - Provision and manage MySQL database clusters
* [orgs](/docs/flyctl/orgs/) - Commands for managing Fly organizations
* [ping](/docs/flyctl/ping/) - Test connectivity with ICMP ping messages
* [platform](/docs/flyctl/platform/) - Fly platform information
-* [postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
* [proxy](/docs/flyctl/proxy/) - Proxies connections to a Fly Machine.
* [redis](/docs/flyctl/redis/) - Launch and manage Redis databases managed by Upstash.com
* [releases](/docs/flyctl/releases/) - List app releases
diff --git a/flyctl/cmd/fly_apps_create.md b/flyctl/cmd/fly_apps_create.md
index b0f7f240bc..eb15697c25 100644
--- a/flyctl/cmd/fly_apps_create.md
+++ b/flyctl/cmd/fly_apps_create.md
@@ -16,6 +16,8 @@ fly apps create [flags]
--name string The app name to use
--network string Specify custom network id
-o, --org string The target Fly.io organization
+ --save Save the app name to the config file
+ -y, --yes Accept all confirmations
~~~
## Global Options
diff --git a/flyctl/cmd/fly_auth_docker.md b/flyctl/cmd/fly_auth_docker.md
index 4a132ee79f..340be6e790 100644
--- a/flyctl/cmd/fly_auth_docker.md
+++ b/flyctl/cmd/fly_auth_docker.md
@@ -1,7 +1,8 @@
-Adds registry.fly.io to the docker daemon's authenticated
-registries. This allows you to push images directly to fly from
-the docker cli.
+Adds registry.fly.io to the Docker daemon's authenticated
+registries. This allows you to push images directly to Fly.io from
+the Docker CLI.
+Note: Tokens generated by this command expire after 5 minutes.
## Usage
~~~
diff --git a/flyctl/cmd/fly_certs.md b/flyctl/cmd/fly_certs.md
index 512b8ed8f4..d06edd8aa4 100644
--- a/flyctl/cmd/fly_certs.md
+++ b/flyctl/cmd/fly_certs.md
@@ -9,11 +9,11 @@ fly certs [command] [flags]
~~~
## Available Commands
-* [add](/docs/flyctl/certs-add/) - Add a certificate for an app.
-* [check](/docs/flyctl/certs-check/) - Checks DNS configuration
-* [list](/docs/flyctl/certs-list/) - List certificates for an app.
+* [add](/docs/flyctl/certs-add/) - Add a certificate for an app
+* [check](/docs/flyctl/certs-check/) - Show certificate and DNS status
+* [list](/docs/flyctl/certs-list/) - List certificates for an app
* [remove](/docs/flyctl/certs-remove/) - Removes a certificate from an app
-* [show](/docs/flyctl/certs-show/) - Shows certificate information
+* [setup](/docs/flyctl/certs-setup/) - Shows certificate setup instructions
## Options
diff --git a/flyctl/cmd/fly_certs_check.md b/flyctl/cmd/fly_certs_check.md
index c11ccce499..9659b1829c 100644
--- a/flyctl/cmd/fly_certs_check.md
+++ b/flyctl/cmd/fly_certs_check.md
@@ -1,5 +1,5 @@
-Checks the DNS configuration for the specified hostname.
-Displays results in the same format as the SHOW command.
+Shows detailed certificate information and checks the DNS configuration
+for the specified hostname.
## Usage
~~~
diff --git a/flyctl/cmd/fly_certs_show.md b/flyctl/cmd/fly_certs_setup.md
similarity index 65%
rename from flyctl/cmd/fly_certs_show.md
rename to flyctl/cmd/fly_certs_setup.md
index cc44167d91..db46c97776 100644
--- a/flyctl/cmd/fly_certs_show.md
+++ b/flyctl/cmd/fly_certs_setup.md
@@ -1,9 +1,9 @@
-Shows certificate information for an application.
-Takes hostname as a parameter to locate the certificate.
+Shows setup instructions for configuring DNS records for a certificate.
+Takes hostname as a parameter to show the setup instructions for that certificate.
## Usage
~~~
-fly certs show [flags]
+fly certs setup [flags]
~~~
## Options
@@ -11,7 +11,7 @@ fly certs show [flags]
~~~
-a, --app string Application name
-c, --config string Path to application configuration file
- -h, --help help for show
+ -h, --help help for setup
-j, --json JSON output
~~~
diff --git a/flyctl/cmd/fly_config_validate.md b/flyctl/cmd/fly_config_validate.md
index bb4b8e4d80..56da0b1005 100644
--- a/flyctl/cmd/fly_config_validate.md
+++ b/flyctl/cmd/fly_config_validate.md
@@ -12,6 +12,7 @@ fly config validate [flags]
-a, --app string Application name
-c, --config string Path to application configuration file
-h, --help help for validate
+ -s, --strict Enable strict validation to check for unrecognized sections and keys
~~~
## Global Options
diff --git a/flyctl/cmd/fly_console.md b/flyctl/cmd/fly_console.md
index cb6ca00210..78b53b823d 100644
--- a/flyctl/cmd/fly_console.md
+++ b/flyctl/cmd/fly_console.md
@@ -29,7 +29,7 @@ fly console [flags]
i.e.: --port 80/tcp --port 443:80/tcp:http:tls --port 5432/tcp:pg_tls
To remove a port mapping use '-' as handler, i.e.: --port 80/tcp:-
-r, --region string The target region (see 'flyctl platform regions')
- -s, --select Select the machine on which to execute the console from a list.
+ -s, --select Select the machine and container on which to execute the console from a list.
-u, --user string Unix username to connect as (default "root")
--vm-cpu-kind string The kind of CPU to use ('shared' or 'performance')
--vm-cpus int Number of CPUs
diff --git a/flyctl/cmd/fly_deploy.md b/flyctl/cmd/fly_deploy.md
index ef910447e4..8d0481553e 100644
--- a/flyctl/cmd/fly_deploy.md
+++ b/flyctl/cmd/fly_deploy.md
@@ -16,6 +16,7 @@ fly deploy [WORKING_DIRECTORY] [flags]
--build-only Build but do not deploy
--build-secret stringArray Set of build secrets of NAME=VALUE pairs. Can be specified multiple times. See https://docs.docker.com/engine/reference/commandline/buildx_build/#secret
--build-target string Set the target build stage to build if the Dockerfile has more than one stage
+ --buildkit Deploy using buildkit-based remote builder
--buildpacks-docker-host string Address to docker daemon that will be exposed to the build container.
If not set (or set to empty string) the standard socket location will be used.
Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used.
@@ -30,6 +31,8 @@ fly deploy [WORKING_DIRECTORY] [flags]
- "volume-opt==", can be specified more than once, takes a key-value pair consisting of the option name and its value.
Repeat for each volume in order (comma-separated lists not accepted)
+ --compression string Compression algorithm to use for the image. Options are "zstd" or "gzip". Defaults to "gzip". (default "gzip")
+ --compression-level int Compression level to use for the image. Defaults to 7. (default 7)
-c, --config string Path to application configuration file
--deploy-retries string Number of times to retry a deployment if it fails (default "auto")
--depot string[="true"] Deploy using depot to build the image (default "auto")
@@ -61,6 +64,7 @@ fly deploy [WORKING_DIRECTORY] [flags]
--no-public-ips Do not allocate any new public IP addresses
--now Deploy now without confirmation
--only-machines strings Deploy to machines only with these IDs. Multiple IDs can be specified with comma separated values or by providing the flag multiple times.
+ --primary-region string Override primary region in fly.toml configuration.
--process-groups strings Deploy to machines only in these process groups
--push Push image to registry after build is complete
--recreate-builder Recreate the builder app, if it exists
diff --git a/flyctl/cmd/fly_extensions.md b/flyctl/cmd/fly_extensions.md
index 61671f5849..3ba3b9d4c0 100644
--- a/flyctl/cmd/fly_extensions.md
+++ b/flyctl/cmd/fly_extensions.md
@@ -7,8 +7,6 @@ fly extensions [command] [flags]
## Available Commands
* [arcjet](/docs/flyctl/extensions-arcjet/) - Provision and manage Arcjet
-* [enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
-* [kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
* [kubernetes](/docs/flyctl/extensions-kubernetes/) - Provision and manage Kubernetes clusters
* [mysql](/docs/flyctl/extensions-mysql/) - Provision and manage MySQL database clusters
* [sentry](/docs/flyctl/extensions-sentry/) - Setup a Sentry project for this app
diff --git a/flyctl/cmd/fly_extensions_enveloop.md b/flyctl/cmd/fly_extensions_enveloop.md
deleted file mode 100644
index 9a93b7d049..0000000000
--- a/flyctl/cmd/fly_extensions_enveloop.md
+++ /dev/null
@@ -1,33 +0,0 @@
-Provision and manage Enveloop projects
-
-
-## Usage
-~~~
-fly extensions enveloop [command] [flags]
-~~~
-
-## Available Commands
-* [create](/docs/flyctl/extensions-enveloop-create/) - Provision an Enveloop project
-* [dashboard](/docs/flyctl/extensions-enveloop-dashboard/) - Open the Enveloop dashboard via your web browser
-* [destroy](/docs/flyctl/extensions-enveloop-destroy/) - Permanently destroy an Enveloop project
-* [list](/docs/flyctl/extensions-enveloop-list/) - List your Enveloop projects
-* [status](/docs/flyctl/extensions-enveloop-status/) - Show details about an Enveloop project
-
-## Options
-
-~~~
- -h, --help help for enveloop
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions](/docs/flyctl/extensions/) - Extensions are additional functionality that can be added to your Fly apps
-
diff --git a/flyctl/cmd/fly_extensions_enveloop_create.md b/flyctl/cmd/fly_extensions_enveloop_create.md
deleted file mode 100644
index 711294e797..0000000000
--- a/flyctl/cmd/fly_extensions_enveloop_create.md
+++ /dev/null
@@ -1,32 +0,0 @@
-Provision an Enveloop project
-
-
-## Usage
-~~~
-fly extensions enveloop create [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for create
- -n, --name string The name of your project
- -o, --org string The target Fly.io organization
- -r, --region string The target region (see 'flyctl platform regions')
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
-
diff --git a/flyctl/cmd/fly_extensions_enveloop_dashboard.md b/flyctl/cmd/fly_extensions_enveloop_dashboard.md
deleted file mode 100644
index 7b395a2762..0000000000
--- a/flyctl/cmd/fly_extensions_enveloop_dashboard.md
+++ /dev/null
@@ -1,29 +0,0 @@
-Open the Enveloop dashboard via your web browser
-
-## Usage
-~~~
-fly extensions enveloop dashboard [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for dashboard
- -o, --org string The target Fly.io organization
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
-
diff --git a/flyctl/cmd/fly_extensions_enveloop_destroy.md b/flyctl/cmd/fly_extensions_enveloop_destroy.md
deleted file mode 100644
index 20e9839da8..0000000000
--- a/flyctl/cmd/fly_extensions_enveloop_destroy.md
+++ /dev/null
@@ -1,28 +0,0 @@
-Permanently destroy an Enveloop project
-
-## Usage
-~~~
-fly extensions enveloop destroy [name] [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for destroy
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
-
diff --git a/flyctl/cmd/fly_extensions_enveloop_list.md b/flyctl/cmd/fly_extensions_enveloop_list.md
deleted file mode 100644
index 01774f15ba..0000000000
--- a/flyctl/cmd/fly_extensions_enveloop_list.md
+++ /dev/null
@@ -1,27 +0,0 @@
-List your Enveloop projects
-
-## Usage
-~~~
-fly extensions enveloop list [flags]
-~~~
-
-## Options
-
-~~~
- -h, --help help for list
- -o, --org string The target Fly.io organization
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
-
diff --git a/flyctl/cmd/fly_extensions_kafka.md b/flyctl/cmd/fly_extensions_kafka.md
deleted file mode 100644
index 6ab1467a8e..0000000000
--- a/flyctl/cmd/fly_extensions_kafka.md
+++ /dev/null
@@ -1,34 +0,0 @@
-Provision and manage Upstash Kafka clusters
-
-
-## Usage
-~~~
-fly extensions kafka [command] [flags]
-~~~
-
-## Available Commands
-* [create](/docs/flyctl/extensions-kafka-create/) - Provision a Upstash Kafka cluster
-* [dashboard](/docs/flyctl/extensions-kafka-dashboard/) - Visit the Upstash Kafka dashboard on the Upstash web console
-* [destroy](/docs/flyctl/extensions-kafka-destroy/) - Permanently destroy an Upstash Kafka cluster
-* [list](/docs/flyctl/extensions-kafka-list/) - List your Upstash Kafka clusters
-* [status](/docs/flyctl/extensions-kafka-status/) - Show details about an Upstash Kafka cluster
-* [update](/docs/flyctl/extensions-kafka-update/) - Update an existing Upstash Kafka cluster
-
-## Options
-
-~~~
- -h, --help help for kafka
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions](/docs/flyctl/extensions/) - Extensions are additional functionality that can be added to your Fly apps
-
diff --git a/flyctl/cmd/fly_extensions_kafka_create.md b/flyctl/cmd/fly_extensions_kafka_create.md
deleted file mode 100644
index 5aca47f347..0000000000
--- a/flyctl/cmd/fly_extensions_kafka_create.md
+++ /dev/null
@@ -1,32 +0,0 @@
-Provision a Upstash Kafka cluster
-
-
-## Usage
-~~~
-fly extensions kafka create [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for create
- -n, --name string The name of your cluster
- -o, --org string The target Fly.io organization
- -r, --region string The target region (see 'flyctl platform regions')
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
-
diff --git a/flyctl/cmd/fly_extensions_kafka_dashboard.md b/flyctl/cmd/fly_extensions_kafka_dashboard.md
deleted file mode 100644
index e8c5c57719..0000000000
--- a/flyctl/cmd/fly_extensions_kafka_dashboard.md
+++ /dev/null
@@ -1,29 +0,0 @@
-Visit the Upstash Kafka dashboard on the Upstash web console
-
-## Usage
-~~~
-fly extensions kafka dashboard [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for dashboard
- -o, --org string The target Fly.io organization
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
-
diff --git a/flyctl/cmd/fly_extensions_kafka_destroy.md b/flyctl/cmd/fly_extensions_kafka_destroy.md
deleted file mode 100644
index 09f2a007f3..0000000000
--- a/flyctl/cmd/fly_extensions_kafka_destroy.md
+++ /dev/null
@@ -1,28 +0,0 @@
-Permanently destroy an Upstash Kafka cluster
-
-## Usage
-~~~
-fly extensions kafka destroy [name] [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for destroy
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
-
diff --git a/flyctl/cmd/fly_extensions_kafka_status.md b/flyctl/cmd/fly_extensions_kafka_status.md
deleted file mode 100644
index df5f9dd979..0000000000
--- a/flyctl/cmd/fly_extensions_kafka_status.md
+++ /dev/null
@@ -1,29 +0,0 @@
-Show details about an Upstash Kafka cluster
-
-
-## Usage
-~~~
-fly extensions kafka status [name] [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for status
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
-
diff --git a/flyctl/cmd/fly_extensions_kafka_update.md b/flyctl/cmd/fly_extensions_kafka_update.md
deleted file mode 100644
index b5ec9c3c2e..0000000000
--- a/flyctl/cmd/fly_extensions_kafka_update.md
+++ /dev/null
@@ -1,30 +0,0 @@
-Update an existing Upstash Kafka cluster
-
-
-## Usage
-~~~
-fly extensions kafka update [flags]
-~~~
-
-## Options
-
-~~~
- -a, --app string Application name
- -c, --config string Path to application configuration file
- -h, --help help for update
- -o, --org string The target Fly.io organization
- -y, --yes Accept all confirmations
-~~~
-
-## Global Options
-
-~~~
- -t, --access-token string Fly API Access Token
- --debug Print additional logs and traces
- --verbose Verbose output
-~~~
-
-## See Also
-
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
-
diff --git a/flyctl/cmd/fly_extensions_storage_update.md b/flyctl/cmd/fly_extensions_storage_update.md
index 47c9890060..7c9428b5fb 100644
--- a/flyctl/cmd/fly_extensions_storage_update.md
+++ b/flyctl/cmd/fly_extensions_storage_update.md
@@ -10,8 +10,10 @@ fly extensions storage update [flags]
~~~
-a, --app string Application name
+ --clear-custom-domain Remove a custom domain from a bucket
--clear-shadow Remove an existing shadow bucket
-c, --config string Path to application configuration file
+ --custom-domain string A custom domain name pointing at your bucket
-h, --help help for update
-o, --org string The target Fly.io organization
--private Set a public bucket to be private
diff --git a/flyctl/cmd/fly_image_update.md b/flyctl/cmd/fly_image_update.md
index c6df54c5ec..8bc6ee4040 100644
--- a/flyctl/cmd/fly_image_update.md
+++ b/flyctl/cmd/fly_image_update.md
@@ -13,7 +13,7 @@ fly image update [flags]
-c, --config string Path to application configuration file
-h, --help help for update
--image string Target a specific image
- --skip-health-checks Skip waiting for health checks inbetween VM updates.
+ --skip-health-checks Skip waiting for health checks between VM updates.
-y, --yes Accept all confirmations
~~~
diff --git a/flyctl/cmd/fly_ips.md b/flyctl/cmd/fly_ips.md
index 372d148688..4706b87bef 100644
--- a/flyctl/cmd/fly_ips.md
+++ b/flyctl/cmd/fly_ips.md
@@ -6,6 +6,7 @@ fly ips [command] [flags]
~~~
## Available Commands
+* [allocate](/docs/flyctl/ips-allocate/) - Allocate recommended IP addresses
* [allocate-v4](/docs/flyctl/ips-allocate-v4/) - Allocate an IPv4 address
* [allocate-v6](/docs/flyctl/ips-allocate-v6/) - Allocate an IPv6 address
* [list](/docs/flyctl/ips-list/) - List allocated IP addresses
diff --git a/flyctl/cmd/fly_extensions_enveloop_status.md b/flyctl/cmd/fly_ips_allocate.md
similarity index 55%
rename from flyctl/cmd/fly_extensions_enveloop_status.md
rename to flyctl/cmd/fly_ips_allocate.md
index 87e05929ce..9109dfbe36 100644
--- a/flyctl/cmd/fly_extensions_enveloop_status.md
+++ b/flyctl/cmd/fly_ips_allocate.md
@@ -1,9 +1,8 @@
-Show details about an Enveloop project
-
+Allocate recommended IP addresses for the application
## Usage
~~~
-fly extensions enveloop status [name] [flags]
+fly ips allocate [flags]
~~~
## Options
@@ -11,8 +10,8 @@ fly extensions enveloop status [name] [flags]
~~~
-a, --app string Application name
-c, --config string Path to application configuration file
- -h, --help help for status
- -y, --yes Accept all confirmations
+ -h, --help help for allocate
+ -r, --region string The target region (see 'flyctl platform regions')
~~~
## Global Options
@@ -25,5 +24,5 @@ fly extensions enveloop status [name] [flags]
## See Also
-* [fly extensions enveloop](/docs/flyctl/extensions-enveloop/) - Provision and manage Enveloop projects
+* [fly ips](/docs/flyctl/ips/) - Manage IP addresses for apps
diff --git a/flyctl/cmd/fly_launch.md b/flyctl/cmd/fly_launch.md
index 696a5475dc..e135df6406 100644
--- a/flyctl/cmd/fly_launch.md
+++ b/flyctl/cmd/fly_launch.md
@@ -9,11 +9,12 @@ fly launch [flags]
~~~
--attach Attach this new application to the current application
- --auto-stop string Automatically suspend the app after a period of inactivity. Valid values are 'off', 'stop', and 'suspend (default "stop")
+ --auto-stop string Automatically suspend the app after a period of inactivity. Valid values are 'off', 'stop', and 'suspend' (default "stop")
--build-arg stringArray Set of build time variables in the form of NAME=VALUE pairs. Can be specified multiple times.
--build-only Build but do not deploy
--build-secret stringArray Set of build secrets of NAME=VALUE pairs. Can be specified multiple times. See https://docs.docker.com/engine/reference/commandline/buildx_build/#secret
--build-target string Set the target build stage to build if the Dockerfile has more than one stage
+ --buildkit Deploy using buildkit-based remote builder
--buildpacks-docker-host string Address to docker daemon that will be exposed to the build container.
If not set (or set to empty string) the standard socket location will be used.
Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used.
@@ -28,8 +29,12 @@ fly launch [flags]
- "volume-opt==", can be specified more than once, takes a key-value pair consisting of the option name and its value.
Repeat for each volume in order (comma-separated lists not accepted)
+ --command string The command to override the Docker CND.
+ --compression string Compression algorithm to use for the image. Options are "zstd" or "gzip". Defaults to "gzip". (default "gzip")
+ --compression-level int Compression level to use for the image. Defaults to 7. (default 7)
-c, --config string Path to application configuration file
--copy-config Use the configuration file if present without prompting
+ --db string[="true"] Provision a Postgres database. Options: mpg (managed postgres), upg/legacy (unmanaged postgres), or true (default type)
--deploy-retries string Number of times to retry a deployment if it fails (default "auto")
--depot string[="true"] Deploy using depot to build the image (default "auto")
--depot-scope string The scope of the Depot builder's cache to use (org or app) (default "org")
@@ -67,6 +72,7 @@ fly launch [flags]
--no-create Do not create an app, only generate configuration files
--no-db Skip automatically provisioning a database
--no-deploy Do not immediately deploy the new app after fly launch creates and configures it
+ --no-github-workflow Skip automatically provisioning a GitHub fly deploy workflow
--no-object-storage Skip automatically provisioning an object storage bucket
--no-public-ips Do not allocate any new public IP addresses
--no-redis Skip automatically provisioning a Redis instance
@@ -74,6 +80,7 @@ fly launch [flags]
--only-machines strings Deploy to machines only with these IDs. Multiple IDs can be specified with comma separated values or by providing the flag multiple times.
-o, --org string The target Fly.io organization
--path string Path to the app source root, where fly.toml file will be saved (default ".")
+ --primary-region string Override primary region in fly.toml configuration.
--process-groups strings Deploy to machines only in these process groups
--push Push image to registry after build is complete
--recreate-builder Recreate the builder app, if it exists
@@ -81,6 +88,7 @@ fly launch [flags]
--regions strings Deploy to machines only in these regions. Multiple regions can be specified with comma separated values or by providing the flag multiple times.
--release-command-timeout string Time duration to wait for a release command finish running, or 'none' to disable. (default "5m0s")
--remote-only Perform builds on a remote builder instance instead of using the local docker daemon. This is the default. Use --local-only to build locally.
+ --secret stringArray Set of secrets in the form of NAME=VALUE pairs. Can be specified multiple times.
-s, --signal string Signal to stop the machine with for bluegreen strategy (default: SIGINT)
--smoke-checks Perform smoke checks during deployment (default true)
--strategy string The strategy for replacing running instances. Options are canary, rolling, bluegreen, or immediate. The default strategy is rolling.
@@ -90,6 +98,7 @@ fly launch [flags]
--vm-gpus int Number of GPUs. Must also choose the GPU model with --vm-gpu-kind flag
--vm-memory string Memory (in megabytes) to attribute to the VM
--vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values
+ -v, --volume strings Volume to mount, in the form of :/path/inside/machine[:]
--volume-initial-size int The initial size in GB for volumes created on first deploy
--wait-timeout string Time duration to wait for individual machines to transition states and become healthy. (default "5m0s")
--wg Determines whether communication with remote builders are conducted over wireguard or plain internet(https) (default true)
diff --git a/flyctl/cmd/fly_machine.md b/flyctl/cmd/fly_machine.md
index b777be2948..f530dabdd6 100644
--- a/flyctl/cmd/fly_machine.md
+++ b/flyctl/cmd/fly_machine.md
@@ -18,6 +18,7 @@ fly machine [command] [flags]
* [kill](/docs/flyctl/machine-kill/) - Kill (SIGKILL) a Fly machine
* [leases](/docs/flyctl/machine-leases/) - Manage machine leases
* [list](/docs/flyctl/machine-list/) - List Fly machines
+* [place](/docs/flyctl/machine-place/) - Simulate Machine placements
* [restart](/docs/flyctl/machine-restart/) - Restart one or more Fly machines
* [run](/docs/flyctl/machine-run/) - Run a machine
* [start](/docs/flyctl/machine-start/) - Start one or more Fly machines
diff --git a/flyctl/cmd/fly_machine_place.md b/flyctl/cmd/fly_machine_place.md
new file mode 100644
index 0000000000..f909b1aeb7
--- /dev/null
+++ b/flyctl/cmd/fly_machine_place.md
@@ -0,0 +1,41 @@
+Simulate a batch of Machine placements across multiple regions
+
+
+## Usage
+~~~
+fly machine place [flags]
+~~~
+
+## Options
+
+~~~
+ -c, --config string Path to application configuration file
+ --count int number of machines to place
+ -h, --help help for place
+ --host-dedication-id string The dedication id of the reserved hosts for your organization (if any)
+ -j, --json JSON output
+ -o, --org string The target Fly.io organization
+ --region string comma-delimited list of regions to place machines
+ --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance')
+ --vm-cpus int Number of CPUs
+ --vm-gpu-kind string If set, the GPU model to attach (a100-pcie-40gb, a100-sxm4-80gb, l40s, a10, none)
+ --vm-gpus int Number of GPUs. Must also choose the GPU model with --vm-gpu-kind flag
+ --vm-memory string Memory (in megabytes) to attribute to the VM
+ --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values
+ --volume-name string name of the volume to place machines
+ --volume-size int size of the desired volume to place machines
+ --weights strings comma-delimited list of key=value weights to adjust placement preferences. e.g., 'region=5,spread=10'
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly machine](/docs/flyctl/machine/) - Manage Fly Machines.
+
diff --git a/flyctl/cmd/fly_machine_run.md b/flyctl/cmd/fly_machine_run.md
index a33a584246..0d2c1810ca 100644
--- a/flyctl/cmd/fly_machine_run.md
+++ b/flyctl/cmd/fly_machine_run.md
@@ -16,6 +16,7 @@ fly machine run [command] [flags]
--build-nixpacks Build your image with nixpacks
--command string Used with --shell. The command to run, if we're shelling into the Machine now (in case you don't have bash). (default "/bin/bash")
-c, --config string Path to application configuration file
+ --container string Container to update with the new image, files, etc; defaults to "app" or the first container in the config.
--detach Return immediately instead of monitoring deployment progress
--dockerfile string The path to a Dockerfile. Defaults to the Dockerfile in the working directory.
--entrypoint string The command to override the Docker ENTRYPOINT.
diff --git a/flyctl/cmd/fly_machine_update.md b/flyctl/cmd/fly_machine_update.md
index b0a094425d..cb46fa1ea4 100644
--- a/flyctl/cmd/fly_machine_update.md
+++ b/flyctl/cmd/fly_machine_update.md
@@ -14,8 +14,10 @@ fly machine update [machine_id] [flags]
--autostop string[="stop"] Automatically stop a Machine when there are no network requests for it. Options include 'off', 'stop', and 'suspend'. (default "off")
--build-depot Build your image with depot.dev
--build-nixpacks Build your image with nixpacks
+ --buildkit Deploy using buildkit-based remote builder
-C, --command string Command to run
-c, --config string Path to application configuration file
+ --container string Container to update with the new image, files, etc; defaults to "app" or the first container in the config.
--detach Return immediately instead of monitoring deployment progress
--dockerfile string The path to a Dockerfile. Defaults to the Dockerfile in the working directory.
--entrypoint string The command to override the Docker ENTRYPOINT.
diff --git a/flyctl/cmd/fly_mcp.md b/flyctl/cmd/fly_mcp.md
new file mode 100644
index 0000000000..177663fe27
--- /dev/null
+++ b/flyctl/cmd/fly_mcp.md
@@ -0,0 +1,38 @@
+flyctl Model Context Protocol.
+
+
+## Usage
+~~~
+fly mcp [command] [flags]
+~~~
+
+## Available Commands
+* [add](/docs/flyctl/mcp-add/) - [experimental] Add MCP proxy client to a MCP client configuration
+* [destroy](/docs/flyctl/mcp-destroy/) - [experimental] Destroy an MCP stdio server
+* [inspect](/docs/flyctl/mcp-inspect/) - [experimental] Inspect a MCP stdio server
+* [launch](/docs/flyctl/mcp-launch/) - [experimental] Launch an MCP stdio server
+* [list](/docs/flyctl/mcp-list/) - [experimental] List MCP servers
+* [logs](/docs/flyctl/mcp-logs/) - [experimental] Show log for an MCP server
+* [proxy](/docs/flyctl/mcp-proxy/) - [experimental] Start an MCP proxy client
+* [remove](/docs/flyctl/mcp-remove/) - [experimental] Remove MCP proxy client from a MCP client configuration
+* [server](/docs/flyctl/mcp-server/) - [experimental] Start a flyctl MCP server
+* [wrap](/docs/flyctl/mcp-wrap/) - [experimental] Wrap an MCP stdio program
+
+## Options
+
+~~~
+ -h, --help help for mcp
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly](/docs/flyctl/help/) - The Fly.io command line interface
+
diff --git a/flyctl/cmd/fly_mcp_add.md b/flyctl/cmd/fly_mcp_add.md
new file mode 100644
index 0000000000..c5ba14396f
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_add.md
@@ -0,0 +1,40 @@
+[experimental] Add MCP proxy client to a MCP client configuration
+
+
+## Usage
+~~~
+fly mcp add [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --bearer-token Use bearer token for authentication (default true)
+ --claude Add MCP server to the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor Add MCP server to the Cursor client configuration
+ --flycast Use wireguard and flycast for access
+ -h, --help help for add
+ --neovim Add MCP server to the Neovim client configuration
+ --password string Password to authenticate with
+ --server string Name to use for the MCP server in the MCP client configuration
+ --url string URL of the MCP wrapper server
+ --user string User to authenticate with
+ --vscode Add MCP server to the VS Code client configuration
+ --windsurf Add MCP server to the Windsurf client configuration
+ --zed Add MCP server to the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_destroy.md b/flyctl/cmd/fly_mcp_destroy.md
new file mode 100644
index 0000000000..cfc193c4f9
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_destroy.md
@@ -0,0 +1,36 @@
+[experimental] Destroy an MCP stdio server
+
+
+## Usage
+~~~
+fly mcp destroy [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --claude Remove MCP server from to the Claude client configuration
+ --config stringArray Path to the MCP client configuration file
+ --cursor Remove MCP server from to the Cursor client configuration
+ -h, --help help for destroy
+ --neovim Remove MCP server from to the Neovim client configuration
+ --server string Name of the MCP server in the MCP client configuration
+ --vscode Remove MCP server from to the VS Code client configuration
+ --windsurf Remove MCP server from to the Windsurf client configuration
+ -y, --yes Accept all confirmations
+ --zed Remove MCP server from to the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_inspect.md b/flyctl/cmd/fly_mcp_inspect.md
new file mode 100644
index 0000000000..6b6817d933
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_inspect.md
@@ -0,0 +1,45 @@
+[experimental] Inspect a MCP stdio server
+
+
+## Usage
+~~~
+fly mcp inspect [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --bearer-token string Bearer token to authenticate with
+ -b, --bind-addr string Local address to bind to (default "127.0.0.1")
+ --claude Use the configuration for Claude client
+ --config string Path to the MCP client configuration file
+ --cursor Use the configuration for Cursor client
+ -h, --help help for inspect
+ --instance string Use fly-force-instance-id to connect to a specific instance
+ --neovim Use the configuration for Neovim client
+ -p, --password string Password to authenticate with
+ --ping Enable ping for the MCP connection
+ --server string Name of the MCP server in the MCP client configuration
+ --sse Use Server-Sent Events (SSE) for the MCP connection
+ --stream Use streaming for the MCP connection
+ --timeout int Timeout in seconds for the MCP connection
+ --url string URL of the MCP wrapper server
+ -u, --user string User to authenticate with
+ --vscode Use the configuration for VS Code client
+ --windsurf Use the configuration for Windsurf client
+ --zed Use the configuration for Zed client
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_launch.md b/flyctl/cmd/fly_mcp_launch.md
new file mode 100644
index 0000000000..1bf5245189
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_launch.md
@@ -0,0 +1,57 @@
+[experimental] Launch an MCP stdio server
+
+
+## Usage
+~~~
+fly mcp launch command [flags]
+~~~
+
+## Options
+
+~~~
+ --auto-stop string Automatically suspend the app after a period of inactivity. Valid values are 'off', 'stop', and 'suspend' (default "suspend")
+ --bearer-token Use bearer token for authentication (default true)
+ --claude Add MCP server to the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor Add MCP server to the Cursor client configuration
+ --file-literal stringArray Set of literals in the form of /path/inside/machine=VALUE pairs where VALUE is the content. Can be specified multiple times.
+ --file-local stringArray Set of files in the form of /path/inside/machine= pairs. Can be specified multiple times.
+ --file-secret stringArray Set of secrets in the form of /path/inside/machine=SECRET pairs where SECRET is the name of the secret. Can be specified multiple times.
+ --flycast Use wireguard and flycast for access
+ -h, --help help for launch
+ --host-dedication-id string The dedication id of the reserved hosts for your organization (if any)
+ --image string The image to use for the app
+ -i, --inspector Launch MCP inspector: a developer tool for testing and debugging MCP servers
+ --name string Suggested name for the app
+ --neovim Add MCP server to the Neovim client configuration
+ --org string The organization that will own the app
+ --password string Password to authenticate with
+ -r, --region string The target region. By default, the new volume will be created in the source volume's region.
+ --secret stringArray Set of secrets in the form of NAME=VALUE pairs. Can be specified multiple times.
+ --server string Name to use for the MCP server in the MCP client configuration
+ --setup strings Additional setup commands to run before launching the MCP server
+ --user string User to authenticate with
+ --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance')
+ --vm-cpus int Number of CPUs
+ --vm-gpu-kind string If set, the GPU model to attach (a100-pcie-40gb, a100-sxm4-80gb, l40s, a10, none)
+ --vm-gpus int Number of GPUs. Must also choose the GPU model with --vm-gpu-kind flag
+ --vm-memory string Memory (in megabytes) to attribute to the VM
+ --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values
+ -v, --volume strings Volume to mount, in the form of :/path/inside/machine[:]
+ --vscode Add MCP server to the VS Code client configuration
+ --windsurf Add MCP server to the Windsurf client configuration
+ --zed Add MCP server to the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_list.md b/flyctl/cmd/fly_mcp_list.md
new file mode 100644
index 0000000000..8bd73a90da
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_list.md
@@ -0,0 +1,35 @@
+[experimental] List MCP servers
+
+
+## Usage
+~~~
+fly mcp list [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --claude List MCP servers from the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor List MCP servers from the Cursor client configuration
+ -h, --help help for list
+ --json Output in JSON format
+ --neovim List MCP servers from the Neovim client configuration
+ --vscode List MCP servers from the VS Code client configuration
+ --windsurf List MCP servers from the Windsurf client configuration
+ --zed List MCP servers from the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_logs.md b/flyctl/cmd/fly_mcp_logs.md
new file mode 100644
index 0000000000..5e9ca428b6
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_logs.md
@@ -0,0 +1,37 @@
+[experimental] Show log for an MCP server
+
+
+## Usage
+~~~
+fly mcp logs [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --claude Select MCP server from the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor Select MCP server from the Cursor client configuration
+ -h, --help help for logs
+ --json Output in JSON format
+ --neovim Select MCP server from the Neovim client configuration
+ -n, --no-tail Do not continually stream logs
+ --server string Name of the MCP server to show logs for
+ --vscode Select MCP server from the VS Code client configuration
+ --windsurf Select MCP server from the Windsurf client configuration
+ --zed Select MCP server from the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_proxy.md b/flyctl/cmd/fly_mcp_proxy.md
new file mode 100644
index 0000000000..01a3606ba3
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_proxy.md
@@ -0,0 +1,38 @@
+[experimental] Start an MCP proxy client
+
+
+## Usage
+~~~
+fly mcp proxy [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --bearer-token string Bearer token to authenticate with
+ -b, --bind-addr string Local address to bind to (default "127.0.0.1")
+ -h, --help help for proxy
+ -i, --inspector Launch MCP inspector: a developer tool for testing and debugging MCP servers
+ --instance string Use fly-force-instance-id to connect to a specific instance
+ -p, --password string Password to authenticate with
+ --ping Enable ping for the MCP connection
+ --sse Use Server-Sent Events (SSE) for the MCP connection
+ --stream Use streaming for the MCP connection
+ --timeout int Timeout in seconds for the MCP connection
+ --url string URL of the MCP wrapper server
+ -u, --user string User to authenticate with
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_remove.md b/flyctl/cmd/fly_mcp_remove.md
new file mode 100644
index 0000000000..1f62a092c9
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_remove.md
@@ -0,0 +1,35 @@
+[experimental] Remove MCP proxy client from a MCP client configuration
+
+
+## Usage
+~~~
+fly mcp remove [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ --claude Remove MCP server from the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor Remove MCP server from the Cursor client configuration
+ -h, --help help for remove
+ --neovim Remove MCP server from the Neovim client configuration
+ --server string Name to use for the MCP server in the MCP client configuration
+ --vscode Remove MCP server from the VS Code client configuration
+ --windsurf Remove MCP server from the Windsurf client configuration
+ --zed Remove MCP server from the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_server.md b/flyctl/cmd/fly_mcp_server.md
new file mode 100644
index 0000000000..6a03e4cf91
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_server.md
@@ -0,0 +1,39 @@
+[experimental] Start a flyctl MCP server
+
+
+## Usage
+~~~
+fly mcp server [flags]
+~~~
+
+## Options
+
+~~~
+ -b, --bind-addr string Local address to bind to (default "127.0.0.1")
+ --claude Add flyctl MCP server to the Claude client configuration
+ --config stringArray Path to the MCP client configuration file (can be specified multiple times)
+ --cursor Add flyctl MCP server to the Cursor client configuration
+ -h, --help help for server
+ -i, --inspector Launch MCP inspector: a developer tool for testing and debugging MCP servers
+ --neovim Add flyctl MCP server to the Neovim client configuration
+ --port int Port to run the MCP server on (default is 8080) (default 8080)
+ --server string Name to use for the MCP server in the MCP client configuration
+ --sse Enable Server-Sent Events (SSE) for MCP commands
+ --stream Enable HTTP streaming output for MCP commands
+ --vscode Add flyctl MCP server to the VS Code client configuration
+ --windsurf Add flyctl MCP server to the Windsurf client configuration
+ --zed Add flyctl MCP server to the Zed client configuration
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mcp_wrap.md b/flyctl/cmd/fly_mcp_wrap.md
new file mode 100644
index 0000000000..b59c262e46
--- /dev/null
+++ b/flyctl/cmd/fly_mcp_wrap.md
@@ -0,0 +1,32 @@
+[experimental] Wrap an MCP stdio program. Options passed after double dashes ("--") will be passed to the MCP program. If user is specified, HTTP authentication will be required.
+
+
+## Usage
+~~~
+fly mcp wrap [flags]
+~~~
+
+## Options
+
+~~~
+ --bearer-token string Bearer token to authenticate with. Defaults to the value of the FLY_MCP_BEARER_TOKEN environment variable.
+ -h, --help help for wrap
+ -m, --mcp string Path to the stdio MCP program to be wrapped.
+ --password string Password to authenticate with. Defaults to the value of the FLY_MCP_PASSWORD environment variable.
+ -p, --port int Port to listen on. (default 8080)
+ --private Use private networking.
+ --user string User to authenticate with. Defaults to the value of the FLY_MCP_USER environment variable.
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mcp](/docs/flyctl/mcp/) - flyctl Model Context Protocol.
+
diff --git a/flyctl/cmd/fly_mpg.md b/flyctl/cmd/fly_mpg.md
new file mode 100644
index 0000000000..e4d73366e0
--- /dev/null
+++ b/flyctl/cmd/fly_mpg.md
@@ -0,0 +1,38 @@
+Manage Managed Postgres clusters.
+
+
+## Usage
+~~~
+fly mpg [command] [flags]
+~~~
+
+## Available Commands
+* [attach](/docs/flyctl/mpg-attach/) - Attach a managed Postgres cluster to an app
+* [backup](/docs/flyctl/mpg-backup/) - Backup commands
+* [connect](/docs/flyctl/mpg-connect/) - Connect to a MPG database using psql
+* [create](/docs/flyctl/mpg-create/) - Create a new Managed Postgres cluster
+* [destroy](/docs/flyctl/mpg-destroy/) - Destroy a managed Postgres cluster
+* [list](/docs/flyctl/mpg-list/) - List MPG clusters.
+* [proxy](/docs/flyctl/mpg-proxy/) - Proxy to a MPG database
+* [restore](/docs/flyctl/mpg-restore/) - Restore MPG cluster from backup.
+* [status](/docs/flyctl/mpg-status/) - Show MPG cluster status.
+
+## Options
+
+~~~
+ -h, --help help for mpg
+ -o, --org string The target Fly.io organization
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly](/docs/flyctl/help/) - The Fly.io command line interface
+
diff --git a/flyctl/cmd/fly_mpg_attach.md b/flyctl/cmd/fly_mpg_attach.md
new file mode 100644
index 0000000000..1448a1f61a
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_attach.md
@@ -0,0 +1,29 @@
+Attach a managed Postgres cluster to an app. This command will add a secret to the specified app
+ containing the connection string for the database.
+
+## Usage
+~~~
+fly mpg attach [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ -c, --config string Path to application configuration file
+ -h, --help help for attach
+ --variable-name string The name of the environment variable that will be added to the attached app (default "DATABASE_URL")
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_backup.md b/flyctl/cmd/fly_mpg_backup.md
new file mode 100644
index 0000000000..7be3097693
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_backup.md
@@ -0,0 +1,30 @@
+Backup commands
+
+
+## Usage
+~~~
+fly mpg backup [command] [flags]
+~~~
+
+## Available Commands
+* [create](/docs/flyctl/mpg-backup-create/) - Create MPG cluster backup.
+* [list](/docs/flyctl/mpg-backup-list/) - List MPG cluster backups.
+
+## Options
+
+~~~
+ -h, --help help for backup
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_backup_create.md b/flyctl/cmd/fly_mpg_backup_create.md
new file mode 100644
index 0000000000..684b5fabfc
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_backup_create.md
@@ -0,0 +1,26 @@
+Create a backup for a Managed Postgres cluster.
+
+## Usage
+~~~
+fly mpg backup create [flags]
+~~~
+
+## Options
+
+~~~
+ -h, --help help for create
+ --type string Backup type: full or incr (default "full")
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg backup](/docs/flyctl/mpg-backup/) - Backup commands
+
diff --git a/flyctl/cmd/fly_mpg_backup_list.md b/flyctl/cmd/fly_mpg_backup_list.md
new file mode 100644
index 0000000000..19bb81f1ba
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_backup_list.md
@@ -0,0 +1,27 @@
+List backups for a Managed Postgres cluster.
+
+## Usage
+~~~
+fly mpg backup list [flags]
+~~~
+
+## Options
+
+~~~
+ --all Show all backups (default: last 24 hours)
+ -h, --help help for list
+ -j, --json JSON output
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg backup](/docs/flyctl/mpg-backup/) - Backup commands
+
diff --git a/flyctl/cmd/fly_mpg_connect.md b/flyctl/cmd/fly_mpg_connect.md
new file mode 100644
index 0000000000..c3b6fca60a
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_connect.md
@@ -0,0 +1,26 @@
+Connect to a MPG database using psql
+
+## Usage
+~~~
+fly mpg connect [flags]
+~~~
+
+## Options
+
+~~~
+ -d, --database string The database to connect to
+ -h, --help help for connect
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_create.md b/flyctl/cmd/fly_mpg_create.md
new file mode 100644
index 0000000000..22505d931c
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_create.md
@@ -0,0 +1,32 @@
+Create a new Managed Postgres cluster
+
+
+## Usage
+~~~
+fly mpg create [flags]
+~~~
+
+## Options
+
+~~~
+ --enable-postgis-support Enable PostGIS for the Postgres cluster
+ -h, --help help for create
+ -n, --name string The name of your Postgres cluster
+ -o, --org string The target Fly.io organization
+ --plan string The plan to use for the Postgres cluster (development, production, etc)
+ -r, --region string The target region (see 'flyctl platform regions')
+ --volume-size int The volume size in GB (default 10)
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_destroy.md b/flyctl/cmd/fly_mpg_destroy.md
new file mode 100644
index 0000000000..dd6e23bdc7
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_destroy.md
@@ -0,0 +1,27 @@
+Destroy a managed Postgres cluster. This command will permanently destroy a managed Postgres cluster and all its data.
+This action is not reversible.
+
+## Usage
+~~~
+fly mpg destroy [flags]
+~~~
+
+## Options
+
+~~~
+ -h, --help help for destroy
+ -y, --yes Accept all confirmations
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_extensions_kafka_list.md b/flyctl/cmd/fly_mpg_list.md
similarity index 50%
rename from flyctl/cmd/fly_extensions_kafka_list.md
rename to flyctl/cmd/fly_mpg_list.md
index 244f7f5822..f44635bd12 100644
--- a/flyctl/cmd/fly_extensions_kafka_list.md
+++ b/flyctl/cmd/fly_mpg_list.md
@@ -1,16 +1,18 @@
-List your Upstash Kafka clusters
+List MPG clusters owned by the specified organization.
+If no organization is specified, the user's personal organization is used.
## Usage
~~~
-fly extensions kafka list [flags]
+fly mpg list [flags]
~~~
## Options
~~~
+ --deleted Show deleted clusters instead of active clusters
-h, --help help for list
+ -j, --json JSON output
-o, --org string The target Fly.io organization
- -y, --yes Accept all confirmations
~~~
## Global Options
@@ -23,5 +25,5 @@ fly extensions kafka list [flags]
## See Also
-* [fly extensions kafka](/docs/flyctl/extensions-kafka/) - Provision and manage Upstash Kafka clusters
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
diff --git a/flyctl/cmd/fly_mpg_proxy.md b/flyctl/cmd/fly_mpg_proxy.md
new file mode 100644
index 0000000000..67225b67ec
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_proxy.md
@@ -0,0 +1,26 @@
+Proxy to a MPG database
+
+## Usage
+~~~
+fly mpg proxy [flags]
+~~~
+
+## Options
+
+~~~
+ -b, --bind-addr string Local address to bind to (default "127.0.0.1")
+ -h, --help help for proxy
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_restore.md b/flyctl/cmd/fly_mpg_restore.md
new file mode 100644
index 0000000000..49d4e51e27
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_restore.md
@@ -0,0 +1,26 @@
+Restore a Managed Postgres cluster from a backup.
+
+## Usage
+~~~
+fly mpg restore [flags]
+~~~
+
+## Options
+
+~~~
+ --backup-id string The backup ID to restore from
+ -h, --help help for restore
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_mpg_status.md b/flyctl/cmd/fly_mpg_status.md
new file mode 100644
index 0000000000..b5a3ccb2c2
--- /dev/null
+++ b/flyctl/cmd/fly_mpg_status.md
@@ -0,0 +1,26 @@
+Show status and details of a specific Managed Postgres cluster using its ID.
+
+## Usage
+~~~
+fly mpg status [CLUSTER_ID] [flags]
+~~~
+
+## Options
+
+~~~
+ -h, --help help for status
+ -j, --json JSON output
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly mpg](/docs/flyctl/mpg/) - Manage Managed Postgres clusters.
+
diff --git a/flyctl/cmd/fly_platform_regions.md b/flyctl/cmd/fly_platform_regions.md
index b6e98a3797..344fd3fd82 100644
--- a/flyctl/cmd/fly_platform_regions.md
+++ b/flyctl/cmd/fly_platform_regions.md
@@ -1,4 +1,5 @@
-View a list of regions where Fly has edges and/or datacenters
+View a list of regions where Fly has datacenters.
+'Capacity' shows how many performance-1x VMs can currently be launched in each region.
## Usage
diff --git a/flyctl/cmd/fly_postgres.md b/flyctl/cmd/fly_postgres.md
index 79d3c237c8..60023f23f1 100644
--- a/flyctl/cmd/fly_postgres.md
+++ b/flyctl/cmd/fly_postgres.md
@@ -1,4 +1,5 @@
-Manage Postgres clusters.
+Unmanaged Fly Postgres is not supported by Fly.io Support and users are responsible for operations, management, and disaster recovery. If you'd like a managed, supported solution, try 'fly mpg' (Managed Postgres).
+Please visit https://fly.io/docs/mpg/overview/ for more information about Managed Postgres.
## Usage
diff --git a/flyctl/cmd/fly_postgres_attach.md b/flyctl/cmd/fly_postgres_attach.md
index c27e834d57..33c2868727 100644
--- a/flyctl/cmd/fly_postgres_attach.md
+++ b/flyctl/cmd/fly_postgres_attach.md
@@ -29,5 +29,5 @@ fly postgres attach [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_backup.md b/flyctl/cmd/fly_postgres_backup.md
index ebfec301ff..f693d8c0a0 100644
--- a/flyctl/cmd/fly_postgres_backup.md
+++ b/flyctl/cmd/fly_postgres_backup.md
@@ -29,5 +29,5 @@ fly postgres backup [command] [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_config.md b/flyctl/cmd/fly_postgres_config.md
index 1aabc983a2..6cd5a26bba 100644
--- a/flyctl/cmd/fly_postgres_config.md
+++ b/flyctl/cmd/fly_postgres_config.md
@@ -26,5 +26,5 @@ fly postgres config [command] [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_connect.md b/flyctl/cmd/fly_postgres_connect.md
index 888af33f24..c868e637d0 100644
--- a/flyctl/cmd/fly_postgres_connect.md
+++ b/flyctl/cmd/fly_postgres_connect.md
@@ -27,5 +27,5 @@ fly postgres connect [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_create.md b/flyctl/cmd/fly_postgres_create.md
index 5dbc957b1c..ced0a81153 100644
--- a/flyctl/cmd/fly_postgres_create.md
+++ b/flyctl/cmd/fly_postgres_create.md
@@ -44,5 +44,5 @@ fly postgres create [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_db.md b/flyctl/cmd/fly_postgres_db.md
index aa88be6151..a2957bd9a0 100644
--- a/flyctl/cmd/fly_postgres_db.md
+++ b/flyctl/cmd/fly_postgres_db.md
@@ -26,5 +26,5 @@ fly postgres db [command] [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_detach.md b/flyctl/cmd/fly_postgres_detach.md
index 962f4f5566..38c3b8d5f2 100644
--- a/flyctl/cmd/fly_postgres_detach.md
+++ b/flyctl/cmd/fly_postgres_detach.md
@@ -24,5 +24,5 @@ fly postgres detach [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_events.md b/flyctl/cmd/fly_postgres_events.md
index f5e20ee539..9e4268c3f4 100644
--- a/flyctl/cmd/fly_postgres_events.md
+++ b/flyctl/cmd/fly_postgres_events.md
@@ -26,5 +26,5 @@ fly postgres events [command] [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_failover.md b/flyctl/cmd/fly_postgres_failover.md
index 70e6a7e704..bbacb9e7c9 100644
--- a/flyctl/cmd/fly_postgres_failover.md
+++ b/flyctl/cmd/fly_postgres_failover.md
@@ -26,5 +26,5 @@ fly postgres failover [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_import.md b/flyctl/cmd/fly_postgres_import.md
index f72f55bf90..f9a1b0b20d 100644
--- a/flyctl/cmd/fly_postgres_import.md
+++ b/flyctl/cmd/fly_postgres_import.md
@@ -31,5 +31,5 @@ fly postgres import [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_list.md b/flyctl/cmd/fly_postgres_list.md
index 51daa23f44..53dd25ae30 100644
--- a/flyctl/cmd/fly_postgres_list.md
+++ b/flyctl/cmd/fly_postgres_list.md
@@ -23,5 +23,5 @@ fly postgres list [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_renew-certs.md b/flyctl/cmd/fly_postgres_renew-certs.md
index 163aad851f..a0de7b45bb 100644
--- a/flyctl/cmd/fly_postgres_renew-certs.md
+++ b/flyctl/cmd/fly_postgres_renew-certs.md
@@ -24,5 +24,5 @@ fly postgres renew-certs [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_restart.md b/flyctl/cmd/fly_postgres_restart.md
index 002cd29388..de8a789260 100644
--- a/flyctl/cmd/fly_postgres_restart.md
+++ b/flyctl/cmd/fly_postgres_restart.md
@@ -26,5 +26,5 @@ fly postgres restart [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_postgres_users.md b/flyctl/cmd/fly_postgres_users.md
index c6d9c91dce..d09e69279e 100644
--- a/flyctl/cmd/fly_postgres_users.md
+++ b/flyctl/cmd/fly_postgres_users.md
@@ -25,5 +25,5 @@ fly postgres users [command] [flags]
## See Also
-* [fly postgres](/docs/flyctl/postgres/) - Manage Postgres clusters.
+* [fly postgres](/docs/flyctl/postgres/) - Unmanaged Postgres cluster commands
diff --git a/flyctl/cmd/fly_scale_vm.md b/flyctl/cmd/fly_scale_vm.md
index ee343d1200..480ffbb845 100644
--- a/flyctl/cmd/fly_scale_vm.md
+++ b/flyctl/cmd/fly_scale_vm.md
@@ -1,9 +1,10 @@
Change an application's VM size to one of the named VM sizes.
-For a full list of supported sizes use the command 'flyctl platform vm-sizes'
+For a full list of supported sizes use the command `flyctl platform vm-sizes`.
-Memory size can be set with --memory=number-of-MB
-e.g. flyctl scale vm shared-cpu-1x --memory=2048
+Memory size can be set with the `--vm-memory` flag followed by the number of MB.
+
+For example: `flyctl scale vm shared-cpu-1x --vm-memory=2048`.
For pricing, see https://fly.io/docs/about/pricing/
diff --git a/flyctl/cmd/fly_secrets.md b/flyctl/cmd/fly_secrets.md
index 63a08de02c..f70d0338e4 100644
--- a/flyctl/cmd/fly_secrets.md
+++ b/flyctl/cmd/fly_secrets.md
@@ -13,6 +13,7 @@ fly secrets [command] [flags]
* [import](/docs/flyctl/secrets-import/) - Set secrets as NAME=VALUE pairs from stdin
* [list](/docs/flyctl/secrets-list/) - List application secret names, digests and creation times
* [set](/docs/flyctl/secrets-set/) - Set one or more encrypted secrets for an application
+* [sync](/docs/flyctl/secrets-sync/) - Sync flyctl with the latest versions of app secrets, even if they were set elsewhere
* [unset](/docs/flyctl/secrets-unset/) - Unset one or more encrypted secrets for an application
## Options
diff --git a/flyctl/cmd/fly_secrets_deploy.md b/flyctl/cmd/fly_secrets_deploy.md
index cc8f758581..562da3cd7f 100644
--- a/flyctl/cmd/fly_secrets_deploy.md
+++ b/flyctl/cmd/fly_secrets_deploy.md
@@ -11,6 +11,7 @@ fly secrets deploy [flags]
-a, --app string Application name
-c, --config string Path to application configuration file
--detach Return immediately instead of monitoring deployment progress
+ --dns-checks Perform DNS checks during deployment (default true)
-h, --help help for deploy
~~~
diff --git a/flyctl/cmd/fly_secrets_import.md b/flyctl/cmd/fly_secrets_import.md
index 446a540e7e..c0f077c75b 100644
--- a/flyctl/cmd/fly_secrets_import.md
+++ b/flyctl/cmd/fly_secrets_import.md
@@ -11,6 +11,7 @@ fly secrets import [flags]
-a, --app string Application name
-c, --config string Path to application configuration file
--detach Return immediately instead of monitoring deployment progress
+ --dns-checks Perform DNS checks during deployment (default true)
-h, --help help for import
--stage Set secrets but skip deployment for machine apps
~~~
diff --git a/flyctl/cmd/fly_secrets_set.md b/flyctl/cmd/fly_secrets_set.md
index 3d6ec9667c..92a2573c2f 100644
--- a/flyctl/cmd/fly_secrets_set.md
+++ b/flyctl/cmd/fly_secrets_set.md
@@ -11,6 +11,7 @@ fly secrets set [flags] NAME=VALUE NAME=VALUE ...
-a, --app string Application name
-c, --config string Path to application configuration file
--detach Return immediately instead of monitoring deployment progress
+ --dns-checks Perform DNS checks during deployment (default true)
-h, --help help for set
--stage Set secrets but skip deployment for machine apps
~~~
diff --git a/flyctl/cmd/fly_secrets_sync.md b/flyctl/cmd/fly_secrets_sync.md
new file mode 100644
index 0000000000..19a00e6c1a
--- /dev/null
+++ b/flyctl/cmd/fly_secrets_sync.md
@@ -0,0 +1,30 @@
+Sync flyctl with the latest versions of app secrets, even if they were set elsewhere
+
+## Usage
+~~~
+fly secrets sync [flags]
+~~~
+
+## Options
+
+~~~
+ -a, --app string Application name
+ -c, --config string Path to application configuration file
+ --detach Return immediately instead of monitoring deployment progress
+ --dns-checks Perform DNS checks during deployment (default true)
+ -h, --help help for sync
+ --stage Set secrets but skip deployment for machine apps
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly secrets](/docs/flyctl/secrets/) - Manage application secrets with the set and unset commands.
+
diff --git a/flyctl/cmd/fly_secrets_unset.md b/flyctl/cmd/fly_secrets_unset.md
index b3649028fa..7bc591e1fc 100644
--- a/flyctl/cmd/fly_secrets_unset.md
+++ b/flyctl/cmd/fly_secrets_unset.md
@@ -11,6 +11,7 @@ fly secrets unset [flags] NAME NAME ...
-a, --app string Application name
-c, --config string Path to application configuration file
--detach Return immediately instead of monitoring deployment progress
+ --dns-checks Perform DNS checks during deployment (default true)
-h, --help help for unset
--stage Set secrets but skip deployment for machine apps
~~~
diff --git a/flyctl/cmd/fly_sftp.md b/flyctl/cmd/fly_sftp.md
index 612b9f2cc8..f0600236ba 100644
--- a/flyctl/cmd/fly_sftp.md
+++ b/flyctl/cmd/fly_sftp.md
@@ -8,6 +8,7 @@ fly sftp [command] [flags]
## Available Commands
* [find](/docs/flyctl/sftp-find/) - The SFTP FIND command lists files (from an optional root directory) on a remote VM.
* [get](/docs/flyctl/sftp-get/) - The SFTP GET retrieves a file from a remote VM.
+* [put](/docs/flyctl/sftp-put/) - The SFTP PUT uploads a file to a remote VM.
* [shell](/docs/flyctl/sftp-shell/) - The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM.
## Options
diff --git a/flyctl/cmd/fly_sftp_get.md b/flyctl/cmd/fly_sftp_get.md
index 06fa1ab54f..4d0636c81b 100644
--- a/flyctl/cmd/fly_sftp_get.md
+++ b/flyctl/cmd/fly_sftp_get.md
@@ -2,7 +2,7 @@ The SFTP GET retrieves a file from a remote VM.
## Usage
~~~
-fly sftp get [flags]
+fly sftp get [local-path] [flags]
~~~
## Options
@@ -19,6 +19,7 @@ fly sftp get [flags]
-g, --process-group string The target process group
--pty Allocate a pseudo-terminal (default: on when no command is provided)
-q, --quiet Don't print progress indicators for WireGuard
+ -R, --recursive Download directories recursively
-r, --region string The target region (see 'flyctl platform regions')
-s, --select select available instances
-u, --user string Unix username to connect as (default "root")
diff --git a/flyctl/cmd/fly_sftp_put.md b/flyctl/cmd/fly_sftp_put.md
new file mode 100644
index 0000000000..6720fab9d0
--- /dev/null
+++ b/flyctl/cmd/fly_sftp_put.md
@@ -0,0 +1,40 @@
+The SFTP PUT uploads a file to a remote VM.
+
+## Usage
+~~~
+fly sftp put [remote-path] [flags]
+~~~
+
+## Options
+
+~~~
+ -A, --address string Address of VM to connect to
+ -a, --app string Application name
+ -C, --command string command to run on SSH session
+ -c, --config string Path to application configuration file
+ --container string Container to connect to
+ -h, --help help for put
+ --machine string Run the console in the existing machine with the specified ID
+ -m, --mode string File mode/permissions for the uploaded file (default: 0644) (default "0644")
+ -o, --org string The target Fly.io organization
+ -g, --process-group string The target process group
+ --pty Allocate a pseudo-terminal (default: on when no command is provided)
+ -q, --quiet Don't print progress indicators for WireGuard
+ -R, --recursive Upload directories recursively
+ -r, --region string The target region (see 'flyctl platform regions')
+ -s, --select select available instances
+ -u, --user string Unix username to connect as (default "root")
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly sftp](/docs/flyctl/sftp/) - Get or put files from a remote VM.
+
diff --git a/flyctl/cmd/fly_ssh_sftp.md b/flyctl/cmd/fly_ssh_sftp.md
index e2918ad823..a39df933c5 100644
--- a/flyctl/cmd/fly_ssh_sftp.md
+++ b/flyctl/cmd/fly_ssh_sftp.md
@@ -8,6 +8,7 @@ fly ssh sftp [command] [flags]
## Available Commands
* [find](/docs/flyctl/ssh-sftp-find/) - The SFTP FIND command lists files (from an optional root directory) on a remote VM.
* [get](/docs/flyctl/ssh-sftp-get/) - The SFTP GET retrieves a file from a remote VM.
+* [put](/docs/flyctl/ssh-sftp-put/) - The SFTP PUT uploads a file to a remote VM.
* [shell](/docs/flyctl/ssh-sftp-shell/) - The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM.
## Options
diff --git a/flyctl/cmd/fly_ssh_sftp_get.md b/flyctl/cmd/fly_ssh_sftp_get.md
index a628368b55..646dad5453 100644
--- a/flyctl/cmd/fly_ssh_sftp_get.md
+++ b/flyctl/cmd/fly_ssh_sftp_get.md
@@ -2,7 +2,7 @@ The SFTP GET retrieves a file from a remote VM.
## Usage
~~~
-fly ssh sftp get [flags]
+fly ssh sftp get [local-path] [flags]
~~~
## Options
@@ -19,6 +19,7 @@ fly ssh sftp get [flags]
-g, --process-group string The target process group
--pty Allocate a pseudo-terminal (default: on when no command is provided)
-q, --quiet Don't print progress indicators for WireGuard
+ -R, --recursive Download directories recursively
-r, --region string The target region (see 'flyctl platform regions')
-s, --select select available instances
-u, --user string Unix username to connect as (default "root")
diff --git a/flyctl/cmd/fly_ssh_sftp_put.md b/flyctl/cmd/fly_ssh_sftp_put.md
new file mode 100644
index 0000000000..07812128af
--- /dev/null
+++ b/flyctl/cmd/fly_ssh_sftp_put.md
@@ -0,0 +1,40 @@
+The SFTP PUT uploads a file to a remote VM.
+
+## Usage
+~~~
+fly ssh sftp put [remote-path] [flags]
+~~~
+
+## Options
+
+~~~
+ -A, --address string Address of VM to connect to
+ -a, --app string Application name
+ -C, --command string command to run on SSH session
+ -c, --config string Path to application configuration file
+ --container string Container to connect to
+ -h, --help help for put
+ --machine string Run the console in the existing machine with the specified ID
+ -m, --mode string File mode/permissions for the uploaded file (default: 0644) (default "0644")
+ -o, --org string The target Fly.io organization
+ -g, --process-group string The target process group
+ --pty Allocate a pseudo-terminal (default: on when no command is provided)
+ -q, --quiet Don't print progress indicators for WireGuard
+ -R, --recursive Upload directories recursively
+ -r, --region string The target region (see 'flyctl platform regions')
+ -s, --select select available instances
+ -u, --user string Unix username to connect as (default "root")
+~~~
+
+## Global Options
+
+~~~
+ -t, --access-token string Fly API Access Token
+ --debug Print additional logs and traces
+ --verbose Verbose output
+~~~
+
+## See Also
+
+* [fly ssh sftp](/docs/flyctl/ssh-sftp/) - Get or put files from a remote VM.
+
diff --git a/flyctl/cmd/fly_storage_update.md b/flyctl/cmd/fly_storage_update.md
index 1fd7ae52ff..03c157d57c 100644
--- a/flyctl/cmd/fly_storage_update.md
+++ b/flyctl/cmd/fly_storage_update.md
@@ -10,8 +10,10 @@ fly storage update [flags]
~~~
-a, --app string Application name
+ --clear-custom-domain Remove a custom domain from a bucket
--clear-shadow Remove an existing shadow bucket
-c, --config string Path to application configuration file
+ --custom-domain string A custom domain name pointing at your bucket
-h, --help help for update
-o, --org string The target Fly.io organization
--private Set a public bucket to be private
diff --git a/flyctl/cmd/fly_volumes_create.md b/flyctl/cmd/fly_volumes_create.md
index 8bebda8691..8518da81cc 100644
--- a/flyctl/cmd/fly_volumes_create.md
+++ b/flyctl/cmd/fly_volumes_create.md
@@ -17,9 +17,11 @@ fly volumes create [flags]
--no-encryption Do not encrypt the volume contents. Volume contents are encrypted by default.
-r, --region string The target region (see 'flyctl platform regions')
--require-unique-zone Place the volume in a separate hardware zone from existing volumes to help ensure availability (default true)
+ --scheduled-snapshots Enable scheduled automatic snapshots (default true)
-s, --size int The size of volume in gigabytes (default 1)
--snapshot-id string Create the volume from the specified snapshot
--snapshot-retention int Snapshot retention in days (default 5)
+ --unique-zone-app-wide Checks all volumes in app for unique zone handling, instead of only volumes with the same name (which is the default)
--vm-cpu-kind string The kind of CPU to use ('shared' or 'performance')
--vm-cpus int Number of CPUs
--vm-gpu-kind string If set, the GPU model to attach (a100-pcie-40gb, a100-sxm4-80gb, l40s, a10, none)
diff --git a/flyctl/cmd/fly_volumes_fork.md b/flyctl/cmd/fly_volumes_fork.md
index c9b6d8c7f6..7fb4f2c236 100644
--- a/flyctl/cmd/fly_volumes_fork.md
+++ b/flyctl/cmd/fly_volumes_fork.md
@@ -16,6 +16,7 @@ fly volumes fork [flags]
-n, --name string The name of the new volume
-r, --region string The target region. By default, the new volume will be created in the source volume's region.
--require-unique-zone Place the volume in a separate hardware zone from existing volumes. This is the default. (default true)
+ --unique-zone-app-wide Checks all volumes in app for unique zone handling, instead of only volumes with the same name (which is the default)
--vm-cpu-kind string The kind of CPU to use ('shared' or 'performance')
--vm-cpus int Number of CPUs
--vm-gpu-kind string If set, the GPU model to attach (a100-pcie-40gb, a100-sxm4-80gb, l40s, a10, none)
diff --git a/flyctl/cmd/fly_wireguard_create.md b/flyctl/cmd/fly_wireguard_create.md
index 28e2019853..6ac78f35f4 100644
--- a/flyctl/cmd/fly_wireguard_create.md
+++ b/flyctl/cmd/fly_wireguard_create.md
@@ -8,7 +8,8 @@ fly wireguard create [org] [region] [name] [file] [flags]
## Options
~~~
- -h, --help help for create
+ -h, --help help for create
+ --network string Custom network name
~~~
## Global Options
diff --git a/getting-started/essentials.html.md b/getting-started/essentials.html.md
index 81d1142674..7dce8f0641 100644
--- a/getting-started/essentials.html.md
+++ b/getting-started/essentials.html.md
@@ -4,6 +4,10 @@ layout: docs
nav: firecracker
---
+
+
+
+
You really can [get an app up and running in just minutes](https://fly.io/speedrun/) on Fly.io.
But if you want to know a bit more about what's what on the Fly.io platform, then read on. You can skip to the [glossary](#fly-io-glossary) for the tl;dr.
@@ -27,7 +31,7 @@ Fly Launch is our built-from-scratch-for-Fly-Machines orchestrator:
- Create your app with the `fly launch` command. Fly Launch detects your framework and gives you useful defaults.
- Configure your app's deployment and services with the [`fly.toml`](/docs/reference/configuration/) configuration file.
- Deploy your app's Machines as a group with the `fly deploy` command.
-- Scale your app's Machines [horizontally](/docs/apps/scale-count/) or [vertically](/docs/apps/scale-machine/) with the `fly scale` command.
+- Scale your app's Machines [horizontally](/docs/launch/scale-count/) or [vertically](/docs/launch/scale-machine/) with the `fly scale` command. [Scaling based on metrics](/docs/launch/autoscale-by-metric/) is also possible.
Again, Fly Launch is built on Machines: you can use Fly Launch to manage the scale of your application with a single command, or you can interact directly with Machines for fine-grained control.
@@ -55,4 +59,4 @@ Learn more about [Fly Apps](/docs/apps/overview/).
**[Fly Volumes](/docs/volumes/):** Local persistent storage for Fly Machines. Every Fly Volume can be attached to one Machine at a time and belongs to one Fly App.
-**Organizations**: Administrative entities on Fly.io that let you to manage billing, control access by adding and removing members, and share app development environments.
+**[Organizations](/docs/security/org-roles-permissions/)**: Administrative entities on Fly.io that let you to manage billing, control access by adding and removing members, and share app development environments.
diff --git a/getting-started/launch-demo.html.markerb b/getting-started/launch-demo.html.markerb
index edfaf32087..9987b3c885 100644
--- a/getting-started/launch-demo.html.markerb
+++ b/getting-started/launch-demo.html.markerb
@@ -15,6 +15,10 @@ redirect_from:
- /docs/hands-on/next/
---
+
+
+
+
In this step-by-step guide you'll use Fly Launch to deploy a simple "hello fly" demo app to Fly.io.
The first step is installing all the tools you need to work with Fly Launch. Which is one tool: flyctl.
diff --git a/getting-started/troubleshooting.html.md b/getting-started/troubleshooting.html.md
index 43d16f3eaa..1c11124dad 100644
--- a/getting-started/troubleshooting.html.md
+++ b/getting-started/troubleshooting.html.md
@@ -4,8 +4,8 @@ layout: docs
nav: firecracker
---
-
-
+
+
This section gives you some ideas of how to start troubleshooting if your deployment doesn't work as expected. If you're still stuck after reading, then visit our [community forum](https://community.fly.io/) for more help.
@@ -233,6 +233,19 @@ That's because buildpacks come with lots of dependencies to build different stac
That said, if the build used to work, then you can try using a previous, fixed buildpack version so it's back in a known good state.
+### Image Size Limit
+
+If your deployment fails with the `Not enough space to unpack image, possibly exceeds maximum of 8GB uncompressed` error, this is because we have limits on the image size you can use to run your Machine.
+
+For our non-GPU Machines, there's an 8GB maximum rootfs size. This means your images need to be under 8GB to run on these machines. While we do have [Fly GPU Machines](https://fly.io/docs/gpus/) that provide 50GB rootfs size, these might not be your cup of tea. We advise either [reducing the image size](/docs/blueprints/using-base-images-for-faster-deployments/) or storing the image in a Fly volume or an object store:
+
+1. **Fly Volumes**: You can create [Fly volumes](/docs/volumes/) for your machines and download your image to the volumes from somewhere when the volume is empty. If you need to create more machines or volumes, you can fork from the already existing, populated volume.
+
+2. **Object Store**: Another option is to store the image in an object store such as [Tigris](/docs/tigris/), and mount the object storage as read-only to a specified path within your machine. This can be done using something like [S3FS](https://github.com/s3fs-fuse/s3fs-fuse).
+
+
+
+
## Related topics
- [Troubleshoot apps when a host is unavailable](/docs/apps/trouble-host-unavailable/)
diff --git a/gpus/getting-started-gpus.html.md b/gpus/getting-started-gpus.html.md
index db72d55a12..618170891a 100644
--- a/gpus/getting-started-gpus.html.md
+++ b/gpus/getting-started-gpus.html.md
@@ -48,7 +48,7 @@ Currently GPUs are available in the following regions:
- `a10`: `ord`
- `l40s`: `ord`
- `a100-40gb`: `ord`
-- `a100-80gb`: `iad`, `sjc`, `syd`
+- `a100-80gb`: `iad`, `sjc`, `syd`, `ams`
### Change GPU model
diff --git a/gpus/gpu-quickstart.html.markerb b/gpus/gpu-quickstart.html.markerb
index bb8b579add..1d351a57d2 100644
--- a/gpus/gpu-quickstart.html.markerb
+++ b/gpus/gpu-quickstart.html.markerb
@@ -23,7 +23,7 @@ nav: firecracker
- `a10`: `ord`
- `l40s`: `ord`
- `a100-40gb`: `ord`
- - `a100-80gb`: `iad`, `sjc`, `syd`
+ - `a100-80gb`: `iad`, `sjc`, `syd`, `ams`
1. Create or modify the `fly.toml` config file in the project source directory, replacing values with your own:
diff --git a/gpus/index.html.md b/gpus/index.html.md
index 39d98fe463..13faadaf55 100644
--- a/gpus/index.html.md
+++ b/gpus/index.html.md
@@ -33,7 +33,7 @@ Currently GPUs are available in the following regions:
- `a10`: `ord`
- `l40s`: `ord`
- `a100-40gb`: `ord`
-- `a100-80gb`: `iad`, `sjc`, `syd`
+- `a100-80gb`: `iad`, `sjc`, `syd`, `ams`
## Examples
diff --git a/hiring/hiring.html.md b/hiring/hiring.html.md
index 5d18c14e8b..15418fb4a4 100644
--- a/hiring/hiring.html.md
+++ b/hiring/hiring.html.md
@@ -21,7 +21,7 @@ There are other good things about our process:
- We don't time the challenges, and you can get them done in dribs and drabs of your spare time if that's best for you.
- We don't look over your shoulder, can't see your typos, and aren't driving up your cortisol with loaded sighs and "hmms" as you work. We want to see you in your best light.
-- You get to use your own dev setup, which, of course, includes Internet connectivity and the ability to Google things, because that is how all code in the industry is actually written.
+- You get to use your own dev setup, which, of course, includes Internet connectivity and the ability to Google things and work with LLMs, because that is how all code in the industry is actually written.
Our challenges aren't artificial. There are no binary trees to invert. We just extract work we've actually done ourselves, and simplify it to the point where we think it can get done in a few hours. Our challenges all have objective rubrics (that turns out to be the hard part of developing a work-sample challenge!) and we can usually grade them pretty mechanically.
@@ -34,7 +34,7 @@ A typical hiring process for an engineering job at Fly.io looks like this:
1. You send us an email. There's an email address at the bottom of all our job descriptions.
1. We say "hi", lay out our hiring challenges, and give you some time to ask us questions. We're happy to field questions!
1. When you're ready, we deliver you a series of at-home challenges (usually 2-3). You knock them out one at a time. If things go roughly with any of them, we'll let you know.
-1. If you knock out all the challenges, we'll schedule a "work day", our last challenge. That's "day" as in "day in the life" — we don't ask for a full day of your time! During the work day, you'll collaborate with us on Slack on an engineering design project.
+1. If you knock out all the challenges, we'll schedule a 60-90 minute discussion in Slack with one of our team members, which we refer to internally as a structured interview. No coding, no video, no microphones; just a typed discussion in Slack so we can better understand how you think about and collaborate on the kinds of problems we tackle at Fly.io.
1. That's it! If everything goes well, the next step is us working out an offer with you.
## What About Time Limits?
@@ -43,47 +43,47 @@ We don't take delivery time into account when grading challenges. Take a day, ta
We have only two caveats to offer about the amount of time you spend on challenges:
-1. If you really do take a month to do the challenges, our staffing situation might change by the time you're done, and we
+1. If you really do take a month to do the challenges, our staffing situation might change by the time you're done, and we
won't be able to move forward immediately. So: if it's going to be a while before you can get to the challenges, let us know,
and we'll be able to warn you if there's any time sensitivity on the role you're applying to. We don't want to have you doing
challenges for a role that isn't open by the time you're done!
-2. We've said it already but we'll say it again: we've tried to calibrate these challenges (by running them many times) so
+2. We've said it already but we'll say it again: we've tried to calibrate these challenges (by running them many times) so
they take roughly the same amount of time as an interview would. You can put as much time into them as you'd like; we're not
-going to notice. But if you're getting sore about the amount of time they're taking, you should reach out to us and we
-can try to figure out what's happening. Sometimes, you might be overthinking the challenge; other times, the role you're
+going to notice. But if you're getting sore about the amount of time they're taking, you should reach out to us and we
+can try to figure out what's happening. Sometimes, you might be overthinking the challenge; other times, the role you're
applying to might not be a perfect fit for where you're coming from.
## What If I Have Questions?
-You can't lose points by asking us questions. Generally, we look at questions as a positive indicator.
+You can't lose points by asking us questions. Generally, we look at questions as a positive indicator.
There are questions you might ask that we can't answer. That's fine; we'll just give evasive answers. Don't moderate
your questions for our sake. You might be surprised at how much we're willing to cough up.
A lot of hiring systems keep their cards close to the vest, and (deliberately or otherwise) evaluate candidates based
-on their ability to navigate a murky process. That's not us.
+on their ability to navigate a murky process. That's not us.
We want to predict how well you'll function on our team doing real work. In a real working week, if our processes were
-so murky that sniffing them out was a real test of your skill, on like a day-to-day basis, we'd have problems of our
-own to fix. We want to see you in your best light.
+so murky that sniffing them out was a real test of your skill, on like a day-to-day basis, we'd have problems of our
+own to fix. We want to see you in your best light.
## What About Start Dates?
This is important: we can only make offers with start dates 1 month out or
less. If you're looking for a role to start a couple months from now (when
you graduate, etc), you're generally welcome to run through our process and
-get a "soft yes" from us.
+get a "soft yes" from us.
-When you're available to take an offer starting within a month, you can
-ping us, and we'll extend an offer.
+When you're available to take an offer starting within a month, you can
+ping us, and we'll extend an offer if the role is still open.
A word of warning: while we expect to be hiring most of our roles pretty
much continuously, the economy can get spooky. We can't make promises about
-openings several months out from now. An offer is a commitment, and that's
+openings several months out from now. An offer is a commitment, and that's
the one thing we can't do if you're looking to start several months from
now. We'll do our best to stay up-front about this stuff!
## What About Compensation?
-At Fly.io, we're focused on getting pay equity (the fairness kind) embedded as close to the core of our philosophy as possible. We want to eliminate as much bias in the hiring process as we can, and we don't want anyone who works here to second-guess themselves on how hard they negotiated their offer. So, we designed our salaries to be intentionally simple and straightforward: everyone at a given level is offered the same salary and options, regardless of their role, geography, and previous experience.
\ No newline at end of file
+At Fly.io, we're focused on getting pay equity (the fairness kind) embedded as close to the core of our philosophy as possible. We want to eliminate as much bias in the hiring process as we can, and we don't want anyone who works here to second-guess themselves on how hard they negotiated their offer. So, we designed our salaries to be intentionally simple and straightforward: everyone at a given level is offered the same salary and options, regardless of their role, geography, and previous experience.
diff --git a/hiring/index.html.md b/hiring/index.html.md
index 9f94dcfdfd..7a9c453233 100644
--- a/hiring/index.html.md
+++ b/hiring/index.html.md
@@ -6,6 +6,10 @@ toc: false
nav: devjobs
---
+
+
+
+
**We're psyched to talk to you about working with us!** Fly.io is doing
something ambitious: we're building a new public cloud, one designed,
from the jump, for developers, that runs code close to users wherever
diff --git a/hiring/working.html.md b/hiring/working.html.md
index 0832396370..6e1316cc91 100644
--- a/hiring/working.html.md
+++ b/hiring/working.html.md
@@ -14,9 +14,9 @@ Like most companies, we do most of our daily work on a Slack. Slack isn't gr
## We're Committed To Diversity And Mutual Respect
We believe in diverse and equitable teams. We're working together on a hard problem, the work
-itself gets challenging, and our teams won't work without mutual respect. Fly.io is used by people of
+itself gets challenging, and our teams won't work without mutual respect. Fly.io is used by people of
all skill levels, all over the world, from many different backgrounds and with many different goals.
-We're believers in the value of glue work and empathy, with each other and our users.
+We're believers in the value of glue work and empathy, with each other and our users.
## Fly.io Hires Around The World
@@ -28,7 +28,7 @@ We have team members in Europe, Rwanda, South Africa, Mexico, Argentina, Canada,
We'll do our best, but it's tricky. We've got access to some legal resources, but our luck, with H1Bs in particular, hasn't
been great. Whatever we say about visas, remember that it's USCIS making the decisions, and USCIS has a different definition
-of "responsive" than we do.
+of "responsive" than we do.
So the short answer on visa sponsorships is: we'll try, but you should take a job here on the assumption that you'll be working
remote indefinitely.
@@ -47,16 +47,13 @@ roughly like:
* Level 1: Early-career or intern-level demonstrated aptitude. An L1 might need
ongoing support from other team members to deliver work.
-
+
* Level 2: See Level 1 and Level 3, and interpolate.
* Level 3: An L3 can lead a team. We use "L3" and "Senior" interchangeably.
* Staff: We're still working out what this means here. We'll generally
- call out staff roles individually.
-
-We also hire engineering management (EMs). Like most modern tech companies,
-EM is a separate track from line engineering.
+ call out staff roles individually.
## We're Ruthless About Doing Customer-Visible Work
@@ -66,7 +63,7 @@ We want our teams to set their own direction as much as possible, but we'll
It's a platform for shipping our customers apps. It's on 24/7/365.
-We made a decision early on not to stick ops teams with the whole on-call challenge. So we all share it. At our current team size, on-call is 2 days roughly every month and a half or so. Most on-call is quiet! Because we all share the rotation, noisy alarms would drive all of us batty.
+We made a decision early on not to stick ops teams with the whole on-call challenge. So we all share it. At our current team size, on-call is 1 week roughly every 9 months or so. Most on-call is quiet! Because we all share the rotation, noisy alarms would drive all of us batty.
If you get socked with a rough on-call, we'll ask you to take the next day off.
diff --git a/images/Managed_Postgres.png b/images/Managed_Postgres.png
new file mode 100644
index 0000000000..ca5be6ba2d
Binary files /dev/null and b/images/Managed_Postgres.png differ
diff --git a/images/mcp-proxy-wrap-flog.png b/images/mcp-proxy-wrap-flog.png
new file mode 100644
index 0000000000..cad5dd7c26
Binary files /dev/null and b/images/mcp-proxy-wrap-flog.png differ
diff --git a/images/mermaid-diagram-1.png b/images/mermaid-diagram-1.png
new file mode 100644
index 0000000000..b5417d9a79
Binary files /dev/null and b/images/mermaid-diagram-1.png differ
diff --git a/images/mermaid-diagram-2.png b/images/mermaid-diagram-2.png
new file mode 100644
index 0000000000..771caf43b1
Binary files /dev/null and b/images/mermaid-diagram-2.png differ
diff --git a/images/orgtoken.png b/images/orgtoken.png
new file mode 100644
index 0000000000..b9948965bd
Binary files /dev/null and b/images/orgtoken.png differ
diff --git a/index.html.md b/index.html.md
index 005f8ec59d..88fdcc4a66 100644
--- a/index.html.md
+++ b/index.html.md
@@ -35,6 +35,7 @@ brew install flyctl
Security
Fly GPUs
Networking
+
Managed Postgres
Fly Kubernetes
Database & Storage
Monitoring
diff --git a/js/shopify.html.md b/js/shopify.html.markerb
similarity index 72%
rename from js/shopify.html.md
rename to js/shopify.html.markerb
index 5a17efe094..3ec977ac0c 100644
--- a/js/shopify.html.md
+++ b/js/shopify.html.markerb
@@ -7,15 +7,20 @@ order: 3
Fly.io has built in support for [Shopify Remix applications](https://shopify.dev/docs/apps/build): simply [scaffold](https://shopify.dev/docs/apps/build/scaffold-app), [build](https://shopify.dev/docs/apps/build/build?framework=remix), and [launch](https://fly.io/shopify).
-As an example, the [Shopify tutorial app (qrcode)](https://shopify.dev/docs/apps/build/build?framework=remix) can be launched without any modification:
+<%= youtube "https://www.youtube.com/watch?v=F4qL3nCFg3E" %>
+
+-- [https://x.com/i/status/1892986447788966011](https://x.com/i/status/1892986447788966011)
+
+To see this in action, [Scaffold an app](https://shopify.dev/docs/apps/build/scaffold-app) and immediately proceed to fly launch:
```
-git clone https://github.com/Shopify/example-app--qr-code--remix qrcode
-cd qrcode
-shopify app config link
+shopify app init
+cd app
fly launch
```
+(substitute the name you gave to your app for `app` in the `cd` line above)
+
## Configuration
`fly launch` parses the output of [shopify app env show](https://shopify.dev/docs/api/shopify-cli/app/app-env-show).
@@ -30,22 +35,26 @@ The value of `SHOPIFY_API_SECRET` will be set as a [fly secret](https://fly.io/d
SHOPIFY_APP_URL = 'https://….fly.dev'
```
+
+ If you're using the Fly dashboard UI to launch your app directly from GitHub, you'll need to manually update the auto-generated `fly.toml` with these Shopify-related environment variables after the deployment completes.
+
+
## Scaling
-By default, all machines will automatically be configured to stop when not in use, and restart on the next request; this is fine for development purposes, but for production you will need to adjust this to meet [Shopify performance requirements](https://shopify.dev/docs/apps/launch/built-for-shopify/requirements#performance).
+By default, all Machines will automatically be configured to stop when not in use, and restart on the next request; this is fine for development purposes, but for production you will need to adjust this to meet [Shopify performance requirements](https://shopify.dev/docs/apps/launch/built-for-shopify/requirements#performance).
Depending on your requirements:
* [suspend](https://community.fly.io/t/autosuspend-is-here-machine-suspension-is-enabled-everywhere/20942) can generally reduce startup time from hundreds of milliseconds to a single digit number of milliseconds. Some applications may experience time skew issues, and there are other limits, so this is not the default.
* Setting `min_machines_running` to 1 in the [http service section](https://fly.io/docs/reference/configuration/#the-http_service-section) of your `fly.toml` can make sure that there always is a started machine to process requests.
-* setting `auto_stop_machines` to `off` in the [http service section](https://fly.io/docs/reference/configuration/#the-http_service-section) will prevent your machines from being stopped all together.
+* setting `auto_stop_machines` to `off` in the [http service section](https://fly.io/docs/reference/configuration/#the-http_service-section) will prevent your Machines from being stopped all together.
-You can also [Scale Machine CPU and RAM](https://fly.io/docs/launch/scale-machine/), and [Scale the number of machines](https://fly.io/docs/launch/scale-count/).
+You can also [Scale Machine CPU and RAM](https://fly.io/docs/launch/scale-machine/), and [Scale the number of Machines](https://fly.io/docs/launch/scale-count/).
## Database
Shopify's template starts you off with SQLite using Prisma. Of particular note:
- * By virtue of only running on a single machine, SQLlite3 apps will experience brief unavailabilty during deploys. For a typical application without any new migrations, this will be on the order of a few hundred milliseconds.
+ * By virtue of only running on a single Machine, SQLlite3 apps will experience brief unavailabilty during deploys. For a typical application without any new migrations, this will be on the order of a few hundred milliseconds.
* For PostgreSQL applications without any volumes, [rolling deploys](https://fly.io/docs/launch/deploy/#deployment-strategy) are the default, avoiding any downtime.
Additional information is available on [deploying Prisma apps on Fly.io](../prisma/).
diff --git a/js/the-basics/object-storage.html.md b/js/the-basics/object-storage.html.md
index fdf04339a4..f29a51adaa 100644
--- a/js/the-basics/object-storage.html.md
+++ b/js/the-basics/object-storage.html.md
@@ -5,11 +5,11 @@ objective: Provision a Tigris Bucket and access it via a `S3Client`
order: 4
---
-[Tigris](Tigris is a globally caching, S3-compatible object storage service built on Fly.io infrastructure.) is a globally caching, S3-compatible object storage service built on Fly.io infrastructure.
+[Tigris](https://tigrisdata.com) is a globally caching, S3-compatible object storage service built on Fly.io infrastructure.
## Launching a new Application?
-If `@aws-sdk/client-s3` is listed as a dependency in your `package.json`, Tigris will be automatically selected by fly launch. This can be overrided this in the launch UI:
+If `@aws-sdk/client-s3` is listed as a dependency in your `package.json`, Tigris will be automatically selected by fly launch. This can be overridden in the launch UI:

@@ -53,4 +53,4 @@ fly launch --from https://github.com/fly-apps/node-dictaphone.git
## Find out more!
-Now that you are up and running, there is a lot more to explore on the [Tigris Global Object Storage](/docs/tigris/) page. Highlights include public buckets, migrating to Tigris with shadow butckets, Pricing, and AWS API compatibility.
\ No newline at end of file
+Now that you are up and running, there is a lot more to explore on the [Tigris Global Object Storage](/docs/tigris/) page. Highlights include public buckets, migrating to Tigris with shadow buckets, pricing, and AWS API compatibility.
diff --git a/kubernetes/fks-features.html.markerb b/kubernetes/fks-features.html.markerb
index b5ff393d6e..9377cfb73e 100644
--- a/kubernetes/fks-features.html.markerb
+++ b/kubernetes/fks-features.html.markerb
@@ -4,6 +4,10 @@ layout: docs
nav: firecracker
---
+
+
+
+
Fly Kubernetes benefits from the features of the Fly.io platform.
## Fly.io infrastructure
diff --git a/kubernetes/fks-quickstart.html.markerb b/kubernetes/fks-quickstart.html.markerb
index 1a464110b0..a62906ff04 100644
--- a/kubernetes/fks-quickstart.html.markerb
+++ b/kubernetes/fks-quickstart.html.markerb
@@ -4,6 +4,10 @@ layout: docs
nav: firecracker
---
+
+
+
+
Fly Kubernetes is in beta and not recommended for critical production usage. To report issues or provide feedback, email us at beta@fly.io.
diff --git a/languages-and-frameworks/dockerfile.html.markerb b/languages-and-frameworks/dockerfile.html.markerb
index 43f8677b40..41f2d7c9cc 100644
--- a/languages-and-frameworks/dockerfile.html.markerb
+++ b/languages-and-frameworks/dockerfile.html.markerb
@@ -4,6 +4,10 @@ layout: language-and-framework-docs
redirect_from: /docs/getting-started/dockerfile/
---
+
+
+
+
Fly Launch can deploy your app from a [Dockerfile](https://docs.docker.com/reference/dockerfile/+external).
The `fly launch` command detects your Dockerfile, builds it, and then deploys your app. Need some extra config? No sweat, we've got you covered.
diff --git a/languages-and-frameworks/golang.html.markerb b/languages-and-frameworks/golang.html.markerb
index 1fc7f36702..522ee0bc8e 100644
--- a/languages-and-frameworks/golang.html.markerb
+++ b/languages-and-frameworks/golang.html.markerb
@@ -4,6 +4,10 @@ layout: language-and-framework-docs
redirect_from: /docs/getting-started/golang/
---
+
+
+
+
<%= partial "partials/intro", locals: { runtime: "Go" } %>
diff --git a/languages-and-frameworks/ruby.html.markerb b/languages-and-frameworks/ruby.html.markerb
index 31663c6ec6..d0387c6e56 100644
--- a/languages-and-frameworks/ruby.html.markerb
+++ b/languages-and-frameworks/ruby.html.markerb
@@ -154,7 +154,7 @@ The values aren't displayed since they are secret!
## _Deploying to Fly_
-To deploy changes to your app, just run just run:
+To deploy changes to your app, just run:
```cmd
fly deploy
diff --git a/languages-and-frameworks/static.html.markerb b/languages-and-frameworks/static.html.markerb
index 9913485e35..ed8ce47ec1 100644
--- a/languages-and-frameworks/static.html.markerb
+++ b/languages-and-frameworks/static.html.markerb
@@ -4,6 +4,10 @@ layout: language-and-framework-docs
redirect_from: /docs/getting-started/static/
---
+
+
+
+
<%= partial "partials/intro", locals: { runtime: "static", type: "site" } %>
In this demonstration, we'll use [nginx](https://nginx.org), the world's most popular web server to serve a few static files with very little configuration. We'll provide a Dockerfile and our content for Fly to transmogrify into a web server running in a VM.
diff --git a/laravel/index.html.markerb b/laravel/index.html.markerb
index f78100873e..a8fc36c40e 100644
--- a/laravel/index.html.markerb
+++ b/laravel/index.html.markerb
@@ -38,17 +38,52 @@ You should be able to visit `http://localhost:8000` and see the home page.
### Launch
-Next, we'll use the `launch` command to automagically configure your app for Fly.
-
-The `launch` command adds a few files to your code base. Don't worry, it will ask before overwriting anything.
+Next, we'll use the `launch` command to automagically configure your app for Fly and deploy it to the Fly cloud. This will add a few files to your code base--but, don't worry, it will ask before overwriting anything.
**If you haven't already, go ahead and run `fly launch`!**
```cmd
fly launch
```
+This would detect that your project is built with Laravel. Afterwhich it would provide a summary of the default configuration that would be given to your app.
+
+```
+We're about to launch your Laravel app on Fly.io. Here's what you're getting:
+
+Organization: Personal (fly launch defaults to the personal org)
+Name: fly-lara-app (derived from your directory name)
+Region: Paris, France (this is the fastest region for you)
+App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM)
+Postgres:
(not requested)
+Redis: Pay-as-you-go Plan: 10 GB Max Data Size, eviction disabled (determined from app source)
+Tigris: (not requested)
+
+? Do you want to tweak these settings before proceeding? (y/N)
+```
+
+Extensions like [Postgres](/docs/mpg/overview/) ( database ), [Redis](/docs/upstash/redis/) ( cache ), and [Tigris](/docs/tigris/#main-content-start) ( storage ) can also be added by default based on whether there's an existing, similar connection found for your Laravel project.
-When asked if you want to deploy now, say **No**.
+
+**Please note** that these extension apps will also incur their own **usage costs**! You can select "y" in order to adjust the default settings and remove any extension you don't need.
+
+
+#### Launch Configuration
+Once you accept the configuration, this would proceed with **adding [default secrets](https://github.com/superfly/flyctl/blob/master/scanner/laravel.go#L41-L50)**, **deploying the accepted extensions**, and **generating files** on your project:
+1. `.github/workflows/fly-deploy.yml`, `fly.toml` - Configurations specific to hosting on Fly
+2. [.fly directory](https://github.com/fly-apps/dockerfile-laravel/tree/main/resources/views/fly) - A directory containing configuration files for running Nginx/PHP in a container
+3. [Dockerfile](https://github.com/fly-apps/dockerfile-laravel/blob/main/resources/views/dockerfile.blade.php) - Used to build a container image that is run in fly
+4. [.dockerignore](https://github.com/fly-apps/dockerfile-laravel/blob/main/resources/views/fly/dockerignore.blade.php) - Used to ensure certain files don’t make its way into your image
+
+
+With the configuration files and extenstion apps in place, an image is built for your app using your project and the Dockerfile generated for it. This image is then finally used to deploy your app.
+
+#### Success!
+You should be able to visit `https://your-app-name.fly.dev` and see the Laravel demo home page.
+
+<%= partial "docs/languages-and-frameworks/partials/launched" %>
+
+
+## Customizing your app
If you have other environment variables to set, you can edit the `fly.toml` file and add them.
@@ -67,29 +102,15 @@ For sensitive data, you can set **secrets** with the [`fly secrets set`](https:/
fly secrets set SOME_SECRET_KEY=
```
-
-The `fly launch` command will generate a secret with a valid, random value for `APP_KEY`.
-
-### Deploy
-Finally, run `fly deploy` to build and deploy your application!
+### Deploy
-You should be able to visit `https://your-app-name.fly.dev` and see the Laravel demo home page.
+Once you've made changes for your app locally, you can run `fly deploy` to deploy your changes.
-<%= partial "docs/languages-and-frameworks/partials/launched" %>
### Some Notes
-The `fly launch` adds some files to your code base.
-
-**Here is what gets added:**
-
-1. `Dockerfile` - Used to build a container image that is run in fly
-2. `.dockerignore` - Used to ensure certain files don't make its way into your repository
-3. `fly.toml` - Configuration specific to hosting on Fly
-4. `.fly` (formerly `docker`) - A directory containing configuration files for running Nginx/PHP in a container
-
Running `fly launch` (and later `fly deploy`) uses the `Dockerfile` to build a container image, copying your application files into the resulting image.
Fly doesn't care about the state of your git repository - it copies whatever files are present (except for files ignored by `.dockerignore`).
diff --git a/launch/autoscale-by-metric.html.markerb b/launch/autoscale-by-metric.html.markerb
index d9f4c3d75a..b7e7bf145d 100644
--- a/launch/autoscale-by-metric.html.markerb
+++ b/launch/autoscale-by-metric.html.markerb
@@ -44,7 +44,7 @@ fly secrets set -a my-autoscaler --stage FAS_API_TOKEN="FlyV1 ..."
The next auth token you'll need is one that has permissions to read from your organization's Prometheus data on Fly:
```cmd
-fly tokens create readonly -o my-org
+fly tokens create readonly my-org
```
Copy the token and use it as a secret on your autoscaler app:
@@ -126,7 +126,7 @@ You can scale multiple independent applications with the same autoscaler by usin
To enable multi-app scaling, you will need to use an organization-wide auth token rather than an app-specific deploy token:
```cmd
-fly tokens create org -o my-org
+fly tokens create org my-org
```
and then set the resulting token on your autoscaler application:
diff --git a/launch/create.html.markerb b/launch/create.html.markerb
index afb3adc678..561c4a11f5 100644
--- a/launch/create.html.markerb
+++ b/launch/create.html.markerb
@@ -5,6 +5,10 @@ nav: apps
redirect_from: /docs/apps/launch/
---
+
+
+
+
This guide goes into some detail about using Fly Launch to create and configure an app. See [Getting Started](/docs/getting-started) for ways to get going even faster.
Fly Launch is a collection of opinionated Fly.io platform features that help you configure and orchestrate your app as a unit.
diff --git a/launch/scale-count.html.markerb b/launch/scale-count.html.markerb
index 701691fa98..113d66b5f0 100644
--- a/launch/scale-count.html.markerb
+++ b/launch/scale-count.html.markerb
@@ -334,3 +334,10 @@ fly machine destroy --force 0e286039f42e86
```
If you destroy a Machine with a volume attached, the volume remains intact until you either explicitly destroy the volume or destroy the app it belongs to.
+
+## Related reading
+
+- [Scale Machine CPU and RAM](/docs/launch/scale-machine/) How to use vertical scaling.
+- [Autoscale based on metrics](/docs/launch/autoscale-by-metric/) Learn about automatic scaling using metrics.
+- [flyctl scale — CLI reference](/docs/flyctl/scale/) Read a full breakdown of the scale subcommands.
+- [Machine Placement and Regional Capacity](/docs/machines/guides-examples/machine-placement/) Find out how region choice and resource availability affect scaling success.
diff --git a/litefs/index.html.markerb b/litefs/index.html.markerb
index 1671ea8957..8be12899aa 100644
--- a/litefs/index.html.markerb
+++ b/litefs/index.html.markerb
@@ -4,7 +4,7 @@ layout: docs
nav: litefs
---
-**Important:** We are not able to provide support or guidance for this product. Use with caution.
+**Important:** We are not able to provide support or guidance for this product. Use with caution. Do not combine LiteFS with autostop/autostart on Fly Machines. The Fly Proxy’s autoscaler can shut down or restart Machines without any awareness of LiteFS lease ownership or data freshness, which can result in a stale machine winning the lease and LiteFS discarding newer changes and LTX file data—risking rollback and data loss.
LiteFS is a distributed file system that transparently replicates SQLite
databases. You can run your application like it's running against a local
@@ -16,11 +16,9 @@ application on the edge. You can run LiteFS anywhere!
LiteFS is stable and running in production environments. The project is still
pre-1.0 so APIs may change and features could be removed. Please remember that
-all software has bugs so we recommend you set up [regular off-site backups][backup]
+all software has bugs so we recommend you set up [regular off-site backups](/docs/litefs/backup/)
in case of malfunction or disk corruption.
-[backup]: /docs/litefs/backup/
-
## Exploring our guides
@@ -28,14 +26,9 @@ You can get up and running quickly with one of our guides:
- [Speedrun: Adding LiteFS to your app](/docs/litefs/speedrun) the fastest way to get started with LiteFS on Fly.io.
-- [Getting Started on Fly.io][] helps you add LiteFS to an existing application and deploy to Fly.io. This guide
+- [Getting Started on Fly.io](/docs/litefs/getting-started-fly) helps you add LiteFS to an existing application and deploy to Fly.io. This guide
provides more details and explanation than the Speedrun.
-- [Getting Started with Docker][] helps you add LiteFS to an existing application that you want to run outside of Fly.io.
-
-- [How LiteFS Works][] explains the concepts behind LiteFS.
+- [Getting Started with Docker](/docs/litefs/getting-started-docker) helps you add LiteFS to an existing application that you want to run outside of Fly.io.
-[Getting Started on Fly.io]: /docs/litefs/getting-started-fly
-[Getting Started with Docker]: /docs/litefs/getting-started-docker
-[Using LiteFS Cloud for Backups]: /docs/litefs/cloud-backups
-[How LiteFS Works]: /docs/litefs/how-it-works
\ No newline at end of file
+- [How LiteFS Works](/docs/litefs/how-it-works) explains the concepts behind LiteFS.
diff --git a/machines/api/apps-resource.html.markerb b/machines/api/apps-resource.html.markerb
index f669611c79..e37edd6a5f 100644
--- a/machines/api/apps-resource.html.markerb
+++ b/machines/api/apps-resource.html.markerb
@@ -262,6 +262,7 @@ no body
"id": "682kqp6pdno9d543",
"name": "my-app-1",
"machine_count": 3,
+ "volume_count": 2,
"network": "default"
},
...
diff --git a/machines/api/machines-resource.html.markerb b/machines/api/machines-resource.html.markerb
index 625f99f9c7..563939f101 100644
--- a/machines/api/machines-resource.html.markerb
+++ b/machines/api/machines-resource.html.markerb
@@ -1182,8 +1182,9 @@ Properties of the `config` object for Machine configuration. See [Machine proper
- `host_dedication_id`: The ID of the host dedication (group of dedicated hosts) on which to create this Machine. (beta)
- `cpus`: int (nil) - The number of CPU cores this Machine should occupy when it runs. (default `1`)
- `gpus`: int (nil) - The number of GPU cores this Machine should occupy when it runs. (default `1`)
- - `kernel_args`: Optional array of strings. Arguments passed to the kernel.
- `memory_mb`: int (nil) - Memory in megabytes as multiples of 256 (default `256`)
+ - `kernel_args`: Optional array of strings. Arguments passed to the kernel.
+ - `persist_rootfs`: string (nil) - The root filesystem will be persisted across restarts and updates. Possible values are `never` (default), `restart`, and `always`. See [here](/docs/reference/configuration/#persist_rootfs) for details.
---
@@ -1212,10 +1213,16 @@ Properties of the `config` object for Machine configuration. See [Machine proper
- `path`: string - Required. Absolute path on the Machine where the volume should be mounted. For example, `/data`.
- `name`: string - The name of the Volume to attach.
- `extend_threshold_percent`: int - The threshold of storage used on a volume, by percentage, that triggers extending the volume’s size by the value of `add_size_gb`.
- - `add_size_gb`: int - The increment, in GB, by which to extend the volume after reaching the `auto_extend_size_threshold`. Required with `auto_extend_size_increment`. Required with `extend_threshold_percent`.
- - `size_gb_limit`: int - The total amount, in GB, to extend a volume. Optional with `auto_extend_size_increment`. Optional with `extend_threshold_percent`.
+ - `add_size_gb`: int - The increment, in GB, by which to extend the volume after reaching the `extend_threshold_percent`. Required with `extend_threshold_percent`.
+ - `size_gb_limit`: int - The total amount, in GB, to extend a volume. Optional with `extend_threshold_percent`.
- `encrypted`: boolean - Volume is encrypted. Default true.
+
+Volumes are tied to specific physical hosts. A Machine can only mount to a volume that exists on the same host. If you create a Machine first and then create a volume, even in the same region, there's a good chance they'll end up on different hosts. In that case, the volume attachment will fail.
+
+You cannot change which volume a Machine is attached to by updating the Machine's config. If you want to use a different volume, you'll need to destroy the Machine and create a new one that mounts to the desired volume.
+
+
---
**`processes`:** An optional array of objects defining multiple processes to run within a Machine. The Machine will stop if any process exits without error.
diff --git a/machines/api/volumes-resource.html.markerb b/machines/api/volumes-resource.html.markerb
index fc4c93da0e..4560457578 100644
--- a/machines/api/volumes-resource.html.markerb
+++ b/machines/api/volumes-resource.html.markerb
@@ -52,6 +52,7 @@ You can use the Volumes resource to create and delete volumes. A Fly Volume is p
| Property | Type | Description |
| --- | --- | --- |
+| `auto_backup_enabled` | bool | Enable automatic daily snapshots. Default true. |
| `attached_alloc_id` | string | n/a |
| `attached_machine_id` | string | The ID of the Machine that’s attached to the volume. |
| `block_size` | int | The size of each memory block in bytes. |
@@ -65,7 +66,7 @@ You can use the Volumes resource to create and delete volumes. A Fly Volume is p
| `name` | string | The volume name. |
| `region` | string | The region where the volume resides, or the target region for volume create. |
| `size_gb` | int | The size of the volume in GB. |
-| `snapshot_retention` | int | The number of days to retain daily snapshots. Defaults to 5 when not set. Min 1, max 60. |
+| `snapshot_retention` | int | The number of days to retain snapshots. Defaults to 5 when not set. Min 1, max 60. |
| `state` | string | The state of the volume. |
| `zone` | string | The hardware zone on which the volume resides. |
@@ -125,6 +126,7 @@ curl -i -X GET \\
"blocks_avail": 730163,
"fstype": "ext4",
"snapshot_retention": 5,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
},
{
@@ -144,6 +146,7 @@ curl -i -X GET \\
"blocks_avail": 708148,
"fstype": "ext4",
"snapshot_retention": 5,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
}
]
@@ -212,15 +215,16 @@ Create a volume for a specific app according to the configuration provided in th
<% component.with_json_table do %>
| Property | Type | Required | Description |
| --- | --- | --- | --- |
+ | `auto_backup_enabled` | bool | no | Enable automatic daily snapshots. Default true. |
| `compute` | object | no | An optional object defining the compute specifications for the expected Machine size and type that the volume will attach to. |
| `encrypted` | boolean | no | Whether to encrypt the volume. Default true. |
| `name` | string | yes | The name for the new volume. |
- | `region` | string | yes | The target region. Must be in the same region as the Machine you want to attach it to. |
+ | `region` | string | no | The region where the volume will be created. Defaults to the Machine's region if attached. If not attached, defaults to the app's primary region. |
| `size_gb` | int | no | The size of the volume in GB. Default 3. |
| `snapshot_id` | string | no | The ID of the volume snapshot to use to create the new volume. |
| `source_volume_id` | string | no | The ID of the source volume for the volume fork. |
| `require_unique_zone` | boolean | no | If true, we will provision this volume on hardware that doesn't have another volume with the same name on it. The typical pattern is to create as many volumes as you need, all with the same name and `require_unique_zone: true`. This will keep your app available in case a host goes down. This flag can also cause volume creation to fail, in case we have used up all unique zones, in which case you probably want to set it to false. Default true. |
- | `snapshot_retention` | int | no | The number of days to retain daily snapshots. Defaults to 5 when not set. Min 1, max 60. |
+ | `snapshot_retention` | int | no | The number of days to retain snapshots. Defaults to 5 when not set. Min 1, max 60. |
<% end %>
<% end %>
<%= render(CodeSampleComponent.new(title: 'Status: 200 OK – Example response', language: 'json')) do %>
@@ -241,6 +245,7 @@ Create a volume for a specific app according to the configuration provided in th
"blocks_avail": 0,
"fstype": "",
"snapshot_retention": 5,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
}
<% end %>
@@ -308,6 +313,7 @@ Retrieve details about a specific volume by its ID within an app.
"blocks_avail": 730163,
"fstype": "ext4",
"snapshot_retention": 5,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
}
<% end %>
@@ -379,7 +385,8 @@ Update paratemeters on the volume.
<% component.with_json_table do %>
| Property | Type | Required | Description |
| --- | --- | --- | --- |
- | `snapshot_retention` | int | no | The number of days to retain daily snapshots. Defaults to 5 when not set. Min 1, max 60. |
+ | `auto_backup_enabled` | bool | no | Enable automatic daily snapshots. Default true. |
+ | `snapshot_retention` | int | no | The number of days to retain snapshots. Defaults to 5 when not set. Min 1, max 60. |
<% end %>
<% end %>
<%= render(CodeSampleComponent.new(title: 'Status: 200 OK – Example response', language: 'json')) do %>
@@ -400,6 +407,7 @@ Update paratemeters on the volume.
"blocks_avail": 0,
"fstype": "",
"snapshot_retention": 10,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
}
<% end %>
@@ -469,6 +477,7 @@ curl -i -X DELETE \\
"blocks_avail": 0,
"fstype": "",
"snapshot_retention": 0,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
}
<% end %>
@@ -542,6 +551,7 @@ curl -i -X PUT \\
"blocks_avail": 0,
"fstype": "",
"snapshot_retention": 5,
+ "auto_backup_enabled": true,
"host_dedication_key": ""
},
"needs_restart": false
diff --git a/machines/api/working-with-machines-api.html.markerb b/machines/api/working-with-machines-api.html.markerb
index c52dc14c86..3d5abcd12a 100644
--- a/machines/api/working-with-machines-api.html.markerb
+++ b/machines/api/working-with-machines-api.html.markerb
@@ -77,6 +77,8 @@ Typically, 2xx HTTP status codes denote successful operations, 4xx codes imply f
## Rate Limits
-Machines API rate limits apply _per-action_, _per-machine_. The limit is 1 request, per second, per action (i.e. Create Machine, Start Machine etc.) — with a short-term burst limit up to 3 req/s, per action.
+Machines API rate limits apply _per-action_, _per-machine_ and are scoped per identifier. That might be Machine ID or App ID, depending on the type of request. The limit is 1 request, per second, per action (i.e. Create Machine, Start Machine etc.) — with a short-term burst limit up to 3 req/s, per action.
This applies to all actions except [Get Machine](/docs/machines/api/machines-resource/#get-a-machine) which is 5 req/s, with a short-term burst limit up to 10 req/s.
+
+Additionally, app deletions are limited to 100 per minute.
diff --git a/machines/flyctl/fly-machine-run.html.md b/machines/flyctl/fly-machine-run.html.md
index 05ff8708e7..4d9424dc8e 100644
--- a/machines/flyctl/fly-machine-run.html.md
+++ b/machines/flyctl/fly-machine-run.html.md
@@ -438,7 +438,7 @@ The `--standby-for` flag sets the [`config.standbys`](/docs/machines/api-machine
## Start a Machine on a schedule
-Use the `--schedule` flag to set the Machine's [`config.schedule`](/docs/machines/api-machines-resource/#machine-config-object-properties) property, which starts the Machine on a fuzzy `hourly`, `daily`, `weekly`, or `monthly` cycle. This is useful for running Machines that do a finite job, then exit. The Machine is started the first time when you run `fly machine run`, and again once per (approximate) hour, day, week, or month. Scheduled machines cannot be started via flyctl or Machines API commands, they will only run according to the schedule.
+Use the `--schedule` flag to set the Machine's [`config.schedule`](/docs/machines/api-machines-resource/#machine-config-object-properties) property, which starts the Machine on a fuzzy `hourly`, `daily`, `weekly`, or `monthly` cycle. This is useful for running Machines that do a finite job, then exit. The Machine is started the first time when you run `fly machine run`, and again once per (approximate) hour, day, week, or month. For machines created with `fly machine create --schedule`, the first run will need to be manually started, since the cycle begins on the first machine start. Scheduled machines cannot be started via flyctl or Machines API commands, they will only run according to the schedule.
**Important:** If the host on which a stopped Machine resides doesn't have the resources to start it when its scheduled time comes, you'll get an error back. It's up to you to build the appropriate level of redundancy into your apps.
diff --git a/machines/guides-examples/machine-placement.html.md b/machines/guides-examples/machine-placement.html.md
new file mode 100644
index 0000000000..3e929180ac
--- /dev/null
+++ b/machines/guides-examples/machine-placement.html.md
@@ -0,0 +1,92 @@
+---
+title: Machine Placement and Regional Capacity
+layout: docs
+nav: machines
+author: kcmartin
+date: 2025-06-20
+---
+
+
+**When you scale an app on Fly.io, where your machines land depends on regional capacity. Here's how to keep that from surprising you.**
+
+
+Let’s say you run `fly scale count` and tell it to drop 10 machines into `dfw` and 10 more into `iad`. If either of those regions is out of capacity, _the entire scale operation fails_. Nothing gets placed. It’s all or nothing.
+
+Most of the time this just works—`dfw` and `iad` are solid bets for region choices. But some regions, like `sjc`, `gru`, and `bom`, tend to be in high demand. If you hardcode your placement into one of those and cross your fingers, you might find yourself scaling to zero.
+
+### How to check available capacity
+
+Run `fly platform regions`. You’ll get a list of regions and their available CPU cores. These aren’t guarantees, but they’re a decent snapshot of where you’re most likely to succeed.
+
+### Let the scheduler help
+
+For individual machine operations like `fly machine clone` or creating a new machine, you can give the scheduler some leeway. Instead of pinning a single region, you hand it a prioritized list or a geographic group. It tries each in order until it finds one that works.
+
+For example:
+
+```
+fly machine clone -r "iad,dfw"
+```
+
+That says: try `iad`, then fall back to `dfw`.
+
+### Geographic groups and aliases
+
+You can use geographic aliases like `us`, `eu`, or `sa` to fan out across a broader area.
+
+| Alias | Area|
+|---------|------------------------|
+| `apac` | Asia-Pacific |
+| `eu` | Europe |
+| `na` | North America |
+| `sa` | South America |
+| `us`, `usa` | United States |
+
+
+### Examples:
+
+- Try London first, then fall back to anywhere in Europe:
+
+```
+lhr,eu
+```
+
+- Try any US region first, then Europe:
+
+```
+us,eu
+```
+
+- Try any North American region first, then any South American region:
+
+```
+na,sa
+```
+
+
+### Scale commands don't (yet) do this
+
+Right now, flexible placement doesn’t apply to `scale`, `deploy`, or `launch`. If you want to scale across regions with fallbacks, the supported way to do that is by cloning machines individually (or using `fly machine create`), with region preferences.
+
+(We know that’s a bit clunky. We’re working on it.)
+
+### Machines API support
+
+If you're driving placement from the Machines API (`api.machines.dev`), all of this flexibility is available there: prioritized region lists, meta-regions, and `any` placement.
+
+### A word on architecture
+
+This only helps if your app actually works when deployed across multiple regions. That means not assuming low-latency connections between machines, and avoiding coordination patterns that only work in single-region setups.
+
+
+Want to see where your app might run?
+Here's the [current list](/docs/reference/regions/): ams, arn, bom, cdg, dfw, ewr, fra, gru, iad, jnb, lax, lhr, nrt, ord, sin, sjc, syd, yyz.
+
+
+
+### Related reading
+
+- [An Introduction to Fly Machines](/docs/machines/overview/)
+- [Regions Reference](/docs/reference/regions/)
+- [Fly Machines API](/docs/machines/api/)
+- [Get Regions](https://docs.machines.dev/#tag/platform/get/platform/regions)
diff --git a/machines/guides-examples/multi-container-machines.html.markerb b/machines/guides-examples/multi-container-machines.html.markerb
new file mode 100644
index 0000000000..9829332171
--- /dev/null
+++ b/machines/guides-examples/multi-container-machines.html.markerb
@@ -0,0 +1,360 @@
+---
+title: Multi-container Machines
+layout: docs
+order: 28
+nav: machines
+---
+
+Fly Machines support running multiple containers per virtual machine using the `containers` array. This feature is currently available through the Machines API and requires using Pilot as the init system. This capability is useful for co-locating services such as your application server, log shippers, metrics collectors, or sidecars.
+
+## Common Sidecar Use Cases
+
+Sidecars are a powerful way to co-locate supporting services alongside your application. This pattern is especially helpful for apps built with multi-tenant or per-user architectures, such as AI agents, dev environments, or SaaS dashboards. Some practical uses include:
+
+- **Metrics and Logs:** Run a Prometheus exporter or a log shipper like Vector to forward application logs or metrics without bundling that logic into your app.
+- **Secrets Agents:** Use a sidecar to fetch secrets from a provider like Vault or cloud metadata services. This keeps your app container lightweight and secure.
+- **Load Smoothing:** Use nginx or Envoy as a reverse proxy for rate limiting, retries, or graceful error handling. This is great for handling bursts in traffic or external API instability.
+- **Storage or Sync Layers:** Mount and manage local state using sidecars like LiteFS or file sync daemons. You can keep persistent logic separated from your app's logic.
+- **AI Agents or Workers:** For agent-based or queued workloads, you can spin up workers or schedulers in separate containers and coordinate them using `depends_on`.
+
+These patterns mirror what we've seen from customers building production systems on Fly Machines with Pilot and the `containers` array. Each container runs in its own isolated process tree, and Pilot ensures the startup order and health status are respected. While containers share the same kernel and VM, they are isolated at the process and filesystem level.
+
+## Defining Containers
+
+When creating a Machine via the API, you can specify a `containers` array, each containing a [`ContainerConfig`](https://docs.machines.dev/#model/flycontainerconfig). Each container can have its own image, environment variables, health checks, startup commands, attached files, and dependencies.
+
+`flyd` (our [in-house orchestrator](https://fly.io/blog/carving-the-scheduler-out-of-our-orchestrator/)) handles pulling images and making them accessible to Pilot, the init system responsible for running the containers and managing their lifecycle. Pilot builds a dependency graph using container startup conditions and runs containers accordingly.
+
+## Example Configuration
+
+```json
+{
+ "containers": [
+ {
+ "name": "my-app",
+ "image": "registry.fly.io/my-app:latest",
+ "env": {
+ "PORT": "8080"
+ },
+ "healthchecks": [
+ {
+ "http": {
+ "port": 8080,
+ "method": "GET",
+ "path": "/health",
+ "scheme": "http"
+ },
+ "interval": 10,
+ "timeout": 5,
+ "grace_period": 5,
+ "success_threshold": 2,
+ "failure_threshold": 3
+ }
+ ]
+ },
+ {
+ "name": "log-shipper",
+ "image": "registry.fly.io/log-shipper:latest",
+ "env": {
+ "LOG_LEVEL": "info"
+ },
+ "depends_on": [
+ {
+ "name": "my-app",
+ "condition": "healthy"
+ }
+ ]
+ }
+ ]
+}
+```
+
+In this configuration, the `log-shipper` container depends on the `my-app` container being healthy before starting.
+
+## Container Dependencies
+
+You can define dependencies between containers using the `depends_on` field. Supported conditions include:
+
+```json
+"depends_on": [
+ {
+ "name": "sidecar1",
+ "condition": "healthy"
+ }
+]
+```
+
+Valid values for `condition`:
+
+- `healthy`: Container will wait until the dependency container passes its health checks.
+- `started`: Container will start as soon as the dependency container has started.
+- `exited_successfully`: Container will wait until the dependency container has exited with a success code.
+
+Internally, Pilot models these dependencies as a directed graph and walks it to orchestrate the correct container startup order.
+
+## Health Checks
+
+Each container specifies its own health checks. The system supports different types of health checks:
+
+**TCP health checks**: Checks if a port is accepting connections
+
+```json
+"tcp": {
+ "port": 6379
+}
+```
+
+**HTTP health checks**: Makes HTTP requests to an endpoint
+
+```json
+"http": {
+ "port": 80,
+ "method": "GET",
+ "path": "/",
+ "scheme": "http"
+}
+```
+
+**Exec health checks**: Runs a command inside the container
+
+```json
+"exec": {
+ "command": ["sh", "-c", "pg_isready -U postgres"]
+}
+```
+
+### Health Check Configuration Options
+
+- `name`: string - Unique identifier for the health check
+- `interval`: int - How often to run the check, in seconds
+- `grace_period`: int - Time in seconds to wait after container starts before checks begin
+- `timeout`: int - Maximum time in seconds to wait for a check to complete
+- `success_threshold`: int - Number of consecutive successful checks required to change status to healthy
+- `failure_threshold`: int - Number of consecutive failed checks before marking as unhealthy
+
+## Security Considerations
+
+Containers in a Machine share the same kernel and VM, but are isolated at the process and filesystem level. Failures in one container won't directly crash others, but they don't provide the same level of isolation as across VMs.
+
+## Deploying Multi-container Machines
+
+There are several ways to deploy multi-container machines on Fly.io. Choose the method that best fits your workflow:
+
+### Using the Machines API
+
+You can create multi-container Machines by sending a POST request to the Machines API. For example, using `curl`:
+
+```bash
+curl -X POST "https://api.machines.dev/v1/apps/%3Cyour-app-name%3E/machines" \
+ -H "Authorization: Bearer
" \
+ -H "Content-Type: application/json" \
+ -d @api-config.json
+```
+
+Here, the `api-config.json` file contains the request body for creating a Machine. Unlike the `flyctl` examples in the next sections, the API expects the JSON body to have a `config` wrapper with the `containers` array nested inside:
+
+```json
+{
+ "config": {
+ "containers": [...],
+ ...
+ }
+}
+```
+
+The `config` object can also include other Machine fields like `guest` and `services`.
+
+Refer to the [Machines API documentation](https://docs.machines.dev/#tag/machines) for complete configuration options.
+
+### Using `fly machine run`
+
+Create a multi-container Machine by passing a JSON configuration file (e.g., `cli-config.json`) to `fly machine run`:
+
+```bash
+fly machine run --machine-config cli-config.json
+```
+
+The file should define `containers` at the root level, alongside any additional Machine configuration:
+
+```json
+{
+ "containers": [...],
+ ...
+}
+```
+
+You can also pass Machine configuration options from the command line:
+
+```bash
+fly machine run --machine-config cli-config.json \
+ --autostart=true --autostop=suspend \
+ --port 80:8080/tcp:http --port 443:8080/tcp:http:tls \
+ --vm-cpu-kind shared --vm-cpus 1 --vm-memory 256
+```
+
+
+Note: When using `--machine-config`, your JSON file must include a `containers` array with at least one container that specifies an `image`.
+
+
+### Using `fly launch` & `fly deploy`
+
+Starting with `flyctl v0.3.147`, you can run multiple containers using `fly deploy`. This example demonstrates using a nginx container as a rate limiter and a custom app container.
+
+#### 1. Create `cli-config.json`
+
+```json
+{
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:latest",
+ "files": [
+ {
+ "guest_path": "/etc/nginx/conf.d/default.conf",
+ "local_path": "nginx.conf"
+ }
+ ],
+ "depends_on": [
+ {
+ "name": "echo",
+ "condition": "healthy"
+ }
+ ]
+ },
+ {
+ "name": "echo",
+ "image": "ealen/echo-server",
+ "healthchecks": [
+ {
+ "exec": {
+ "command": ["wget", "-q", "--spider", "http://localhost/"]
+ }
+ }
+ ]
+ }
+ ]
+}
+```
+
+#### 2. Update fly.toml
+
+**Note:** `flyctl` [version 0.3.147](https://github.com/superfly/flyctl/releases/tag/v0.3.147) or later is required to use this config
+
+```
+[experimental]
+machine_config = 'cli-config.json'
+container = 'echo'
+
+[http_service]
+internal_port = 8080
+```
+
+The container value determines which container will be replaced by your app image built by `fly deploy`.
+
+#### 3. Run fly deploy
+
+```bash
+fly deploy
+```
+
+This builds and replaces the `echo` container, runs both containers on the same machine, and configures traffic through nginx.
+
+You can also inline the `machine_config` in your `fly.toml` using triple quotes. The first character inside the string must be `{`:
+
+```
+machine_config = '''{
+ "containers": [
+ …
+ ]
+}'''
+```
+
+## Example Configurations
+
+Here are some practical examples of multi-container setups you can use as a starting point:
+
+### Rate Limiter Sidecar
+
+To mitigate excessive traffic, you can run an nginx container as a sidecar to limit requests per second using the `ngx_http_limit_req_module`. Below is a simplified container configuration that enables rate limiting:
+
+```json
+{
+ "containers": [
+ {
+ "name": "nginx",
+ "image": "nginx:latest",
+ "files": [
+ {
+ "guest_path": "/etc/nginx/conf.d/default.conf",
+ "raw_value": "bGltaXRfcmVxX3pvbmUgJGJpbmFyeV9yZW1vdGVfYWRkciB6b25lPW15X3pvbmU6MTBtIHJhdGU9MXIvczsKCnNlcnZlciB7CiAgbGlzdGVuIDgwODA7CgogIGxvY2F0aW9uIC8gewogICAgbGltaXRfcmVxIHpvbmU9bXlfem9uZTsKICAgIHByb3h5X3Bhc3MgaHR0cDovL2xvY2FsaG9zdDo4MDsKICB9Cn0="
+ }
+ ]
+ },
+ {
+ "name": "echo",
+ "image": "ealen/echo-server"
+ }
+ ]
+}
+```
+
+When attaching files to containers, the `raw_value` field must contain base64-encoded content. The nginx configuration shown in the JSON above corresponds to this configuration:
+
+```
+limit_req_zone $binary_remote_addr zone=my_zone:10m rate=1r/s;
+
+server {
+ listen 8080;
+
+ location / {
+ limit_req zone=my_zone;
+ proxy_pass http://localhost:80;
+ }
+}
+```
+
+When the container starts, Fly will decode this value and write the original configuration to the specified `guest_path`.
+
+To complete this configuration, you should also include:
+
+- A `services` block that exposes internal port 8080 for Fly Proxy
+- A `guest` block specifying resources like CPU and memory
+- Optionally, `depends_on` fields or health checks if you want nginx to wait for the app
+
+### LiteFS Sidecar with Health Checks
+
+You can also run LiteFS as a sidecar with a health check configured to ensure it's operating correctly:
+
+```json
+{
+ "containers": [
+ {
+ "name": "litefs",
+ "image": "flyio/litefs:latest",
+ "env": {
+ "LITEFS_DIR": "/data",
+ "LITEFS_CONFIG": "/etc/litefs.yml"
+ },
+ "healthchecks": [
+ {
+ "http": {
+ "port": 8080,
+ "method": "GET",
+ "path": "/info",
+ "scheme": "http"
+ },
+ "grace_period": 500,
+ "interval": 1000,
+ "timeout": 1000,
+ "success_threshold": 1,
+ "failure_threshold": 5
+ }
+ ]
+ }
+ ]
+}
+```
+
+
+For more information about container configuration options, refer to the [Machines API documentation](https://docs.machines.dev/#model/flycontainerconfig).
+
diff --git a/machines/guides-examples/network-policies.html.markerb b/machines/guides-examples/network-policies.html.markerb
new file mode 100644
index 0000000000..54e8871a97
--- /dev/null
+++ b/machines/guides-examples/network-policies.html.markerb
@@ -0,0 +1,142 @@
+---
+title: Network Policies
+layout: docs
+order: 25
+nav: machines
+---
+
+Network policies let you control traffic to and from your Machines. You can use policies to allow or restrict ingress and egress on specific ports and protocols. This is useful when you're running untrusted code or want to lock down what traffic is allowed.
+
+
+Network policies only apply to traffic directly to and from Machines. They do not affect traffic routed through the Fly Proxy.
+
+
+
+## How it works
+
+A network policy contains:
+
+* A `selector` to match which Machines the policy applies to.
+* A list of `rules` for ingress or egress, specifying allowed ports and protocols.
+
+Once you create a rule for a direction (ingress or egress), the default for that direction becomes "deny all." Only explicitly allowed traffic will be permitted.
+
+## Restricting egress traffic to HTTP/HTTPS
+
+For example, if you're running an app that should only make outbound HTTP and HTTPS requests, you can create a policy like this:
+
+```sh
+curl --location 'https://api.machines.dev/v1/apps/my-app-name/network_policies' \
+ --header 'Authorization: Bearer ' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "name": "specific-egress",
+ "selector": {
+ "all": true
+ },
+ "rules": [
+ {
+ "action": "allow",
+ "direction": "egress",
+ "ports": [
+ { "protocol": "tcp", "port": 80 },
+ { "protocol": "tcp", "port": 443 }
+ ]
+ }
+ ]
+ }'
+```
+
+Replace `` and `my-app-name` with your actual values.
+
+## Selectors
+
+Selectors determine which Machines your network policy applies to. You can target Machines using one or more of the following methods:
+
+### All Machines in an app
+
+To apply a policy to every Machine in your app:
+
+```json
+{ "all": true }
+```
+
+### Specific Machine IDs
+
+To target individual Machines by their unique identifiers:
+
+```json
+{ "machines": [ { "id": "abc" }, { "id": "def" } ] }
+```
+
+### Metadata matching
+
+To select Machines based on their metadata attributes:
+
+```json
+{ "metadata": { "role": "web", "env": "production" } }
+```
+These selection methods can be combined to create more precise targeting rules that match Machines satisfying all specified criteria.
+
+## Rules
+
+Each rule has:
+
+* `action`: Only `allow` is supported.
+* `direction`: Either `ingress` or `egress`.
+* `ports`: A list of port and protocol objects.
+
+Example:
+
+```json
+{
+ "action": "allow",
+ "direction": "egress",
+ "ports": [
+ { "protocol": "tcp", "port": 443 }
+ ]
+}
+```
+
+Once a rule is defined, all other traffic in that direction is denied by default.
+
+## API summary
+
+### Create or update a policy
+
+```http
+POST /v1/apps//network_policies
+```
+
+Request body:
+
+```json
+{
+ "name": "policy-name",
+ "id": "optional-policy-id",
+ "selector": { ... },
+ "rules": [ ... ]
+}
+```
+Include the `id` field to update an existing policy.
+
+### List policies
+
+```http
+GET /v1/apps//network_policies/
+```
+
+### Delete a policy
+
+```http
+DELETE /v1/apps//network_policies/
+```
+
+## Troubleshooting
+
+* After creating or updating a policy, restart or redeploy the Machines for changes to take effect.
+* Use direct IP addresses (not hostnames) to test blocked traffic to avoid DNS masking.
+* Make sure your `selector` is correct and matches the Machines you expect.
+* If traffic is still allowed unexpectedly, check if it's going through the Fly Proxy.
+
+
diff --git a/machines/guides-examples/one-app-per-user-why.html.markerb b/machines/guides-examples/one-app-per-user-why.html.markerb
new file mode 100644
index 0000000000..b6d7ca6989
--- /dev/null
+++ b/machines/guides-examples/one-app-per-user-why.html.markerb
@@ -0,0 +1,33 @@
+---
+title: One App Per Customer - Why?
+layout: docs
+order: 15
+nav: machines
+categories:
+ - machines
+---
+
+We recommend that you create one app, with one or more machines, **for each customer** you have. You'll gain a lot from this:
+
+* you'll be able to leverage Fly.io proxy’s load balancing
+* the `fly.toml` config is app-level, so you can run Machines in different regions / different sizes in the same app
+* your secrets live at the app level: a compromised user app cannot see other user apps' secrets; whereas if all user projects are mashed together in a mega-app, they all have access to everyone else's secrets (plus the `env` would be ginormous and unwieldy)
+* auto start / auto stop runs at the app level
+* apps are easier to transfer between orgs if you ever need move things around
+* there's a cleaner separation of concerns in logs
+* apps get isolated networks (if you pass in a network name), and machines on the same app share a private network—more on this below!
+* each app can still scale independently—so if you've got a _very successful user_ who needs beefier machines, you can scale them up without affecting your other users
+
+A pattern where a single application has machines for all customers is _technically_ possible, but you'd lose the benefits of load balancing, and it would be challenging to make it resilient. Plus, our tooling is not really designed to list thousands of machines per app, so you'd get weird API behavior in some places.
+
+## Isolated networks
+
+Each Fly.io organization gets an isolated network that connects all machines in the organization using Wireguard. This is described in more detail [in our private networking docs](https://fly.io/docs/networking/private-networking/). This generally means that one can deploy, for example, one app for each service, and they will be able to connect to each other using the appropriate hostnames (`.internal` or `.flycast`) as explained in the document.
+
+However, at application creation time, a `--network` parameter can be passed, to create a [custom private network](https://fly.io/docs/networking/custom-private-networks/) for that application. This subnet will not be connected to the organization’s, meaning this application will be isolated from others (although it can still provide a public internet-facing service as configured in `fly.toml`).
+
+The [custom private networks](https://fly.io/docs/networking/custom-private-networks/#private-apps-with-flycast) document details a few ways an isolated app can talk to other applications in a controlled manner, including:
+
+* Application A (non-isolated) can have a `.flycast` IP address in application B’s (isolated) subnet, so they can talk to each other over that IP address. You can do this using `fly ips allocate-v6 --private -a APP_A --network APP_B_NETWORK`. App A can then contact “app-b.flycast” directly.
+* Using the [fly-replay header](https://fly.io/docs/networking/dynamic-request-routing/), an application can direct requests to machines in another application, even if that one resides on a private subnet.
+* If application B (isolated) exposes a public service, then other applications (isolated or not) can access it over the Internet using its public name.
diff --git a/machines/machine-states.html.markerb b/machines/machine-states.html.markerb
index aaec436e3b..7338e367e3 100644
--- a/machines/machine-states.html.markerb
+++ b/machines/machine-states.html.markerb
@@ -1,21 +1,109 @@
---
-title: Machine states
+title: Machine states and lifecycle
layout: docs
nav: machines
-toc: false
+toc: true
---
-This table explains the possible Machine states. A Machine may only be in one state at a time.
-
-| | |
-|----------------|------------------------------------------------------------------------|
-| **created** | Initial status |
-| **starting** | Transitioning from `stopped` or `suspended` to `started` |
-| **started** | Running and network-accessible |
-| **stopping** | Transitioning from `started` to `stopped` |
-| **stopped** | Exited, either on its own or explicitly stopped |
-| **suspending** | Transitioning from `started` to `suspended` |
-| **suspended** | Suspended to disk; will attempt to resume on next start |
-| **replacing** | User-initiated configuration change (image, VM size, etc.) in progress |
-| **destroying** | User asked for the Machine to be completely removed |
-| **destroyed** | No longer exists |
\ No newline at end of file
+Fly Machines go through a series of lifecycle states during creation, updates, shutdown, and deletion. If you're automating deployments, coordinating Machine pools, or writing tools against the Machines API, understanding these states can help you make better decisions.
+
+## Machine state vs. machine version state
+
+Each Machine has a single Machine ID, but every change (like updating the image or resources) creates a new Machine version. It's important to distinguish between:
+
+- Overall Machine state – represents the active version of the Machine.
+- Machine version state – represents the state of a specific version of a Machine.
+
+If you query a Machine without specifying a version, the API returns the current active version's state. If you query an older version, the API may return a terminal state like `replaced`.
+
+## Machine state types
+
+### Persistent states
+
+These states remain until you take action (or something fails).
+
+| State | Description |
+|-------|-------------|
+| `created` | Initial status. Machine has been created but not yet started |
+| `started` | Running and network-accessible |
+| `stopped` | Exited, either on its own or explicitly stopped |
+| `suspended` | Suspended to disk; will attempt to resume on next start |
+| `failed` | Machine encountered an error and could not start successfully |
+
+### Transient states
+
+These are short-lived. The Machine will move to a new state automatically.
+
+| State | Description |
+|-------|-------------|
+| `creating` | Machine is being initialized |
+| `starting` | Transitioning from stopped or suspended to started |
+| `stopping` | Transitioning from started to stopped |
+| `restarting` | Machine is restarting |
+| `suspending` | Transitioning from started to suspended |
+| `destroying` | User asked for the Machine to be completely removed |
+| `launch_failed` | Machine failed to launch; will transition to destroyed |
+| `updating` | Machine is being updated to a new version |
+| `replacing` | User-initiated configuration change (image, VM size, etc.) in progress |
+
+### Terminal states
+
+These are final states that a Machine or its version won't exit from.
+
+| State | Description |
+|-------|-------------|
+| `destroyed` | No longer exists |
+| `replaced` | Machine version was replaced by a newer one (applies to version-specific queries only). If you query an older version by ID, the API will always return replaced for that version, even if the Machine is currently running. |
+| `migrated` | Machine has been moved to a new host; previous version is no longer active |
+
+## Machine state lifecycle diagrams
+
+### Overall Machine state lifecycle
+
+
+
+### Machine version state transitions
+
+
+
+## Update and versioning behavior
+
+When you update a Machine:
+
+1. A new Machine version is created with the updated configuration.
+2. The previous version is marked as `replaced`.
+3. The new version becomes the active version and:
+ - May stay in `created` if `skip_launch` is set to true.
+ - May transition to `started` by default.
+ - May transition to `stopped`, depending on how the update was triggered and the config used.
+
+Machines are launched automatically upon creation or after an update unless `skip_launch` is explicitly set. This flag allows you to create or update a Machine without starting it immediately.
+
+If you query an old version by version ID, you may see `replaced`. To always get the current state, query the Machine without specifying a version.
+
+## Diagnosing stuck Machines
+
+If a Machine stays in a transient state for an extended time, it might be stuck ("wedged"). The following thresholds can help you decide:
+
+- `starting`, `stopping`, `restarting`, or `destroying` > 5 minutes (this can depend on the configured `kill_timeout`)
+- `updating` > 10 minutes
+
+To troubleshoot:
+
+1. Check machine events via the Machines API.
+2. Try stopping and starting the Machine.
+3. If needed, contact Fly.io support with the Machine ID.
+
+## Important Machine state considerations
+
+- `replaced` applies only to old versions and is terminal for that version. If you query an older version by ID, the API will always return `replaced` for that version, even if the Machine is currently running.
+- `migrated` indicates the Machine was moved to a new host and is no longer active.
+- Machines in `suspended` preserve memory and disk state, and resume faster than `stopped`.
+- The `launch_failed` state is usually unrecoverable and transitions to `destroyed`.
+- Machines in `failed` may be recoverable. You can try restarting, stopping, or destroying them.
+
+## Related docs
+
+- [Machines API reference](/docs/machines/api/)
+- [Working with Machines](/docs/machines/)
+- [Fly machine update command](/docs/machines/flyctl/fly-machine-update/)
\ No newline at end of file
diff --git a/machines/overview.html.markerb b/machines/overview.html.markerb
index 60aa7d784d..17d3115cd1 100644
--- a/machines/overview.html.markerb
+++ b/machines/overview.html.markerb
@@ -119,10 +119,10 @@ are; we've already done the heavy lifting.
### Static Machine IPs
-Static egress IPs can be attached to a machine. These IPs survive a [machine migration](https://fly.io/docs/reference/machine-migration/) and are not shared between machines.
+Static egress IPs can be attached to a machine. These IPs survive a [machine migration](/docs/reference/machine-migration/) and are not shared between machines.
Static egress IPs are useful when your machine needs to connect to a service that requires allowlisting a specific set of IPs.
-If supported, it is recommended to use [Wireguard](https://fly.io/docs/networking/private-networking/#private-network-vpn) to connect to external services.
+If supported, it is recommended to use [Wireguard](/docs/networking/private-networking/#private-network-vpn) to connect to external services.
You can attach a static egress IP to your machine with `fly machine egress-ip allocate `
@@ -147,7 +147,9 @@ put the Machine in.
You don't get to pick particular servers (there are ways to cheat and do it anyways, but you shouldn't want to), just the region.
-If you pick a particular region, like `ord` or `yyz`, we will _only_ create the Machine in that region.
+If you pick a particular region, like `ord` or `yyz`, we will _only_ create the Machine in that region.
+
+Read more about Machine placement and regional capacity in [this guide](/docs/machines/guides-examples/machine-placement/).
**Placement can fail!** It shouldn't happen often, but we can run out of capacity in particular regions. Both flyctl and the Fly Machines
@@ -162,3 +164,11 @@ API are best-effort. If you're working with us at this level of control, it's on
* Provide ephemeral storage, a blank slate on every startup
* Attach a volume for persistent storage
* Place in any region
+
+## Related reading
+
+- [Machine states and lifecycle](/docs/machines/machine-states/) Find out how Machines move through states (created → started → stopped etc), what it means, how to detect issues.
+- [Machine placement and regional capacity](/docs/machines/guides-examples/machine-placement/) Learn more about how region and host capacity affect where your Machines land and what to do if placement fails.
+- [`fly machine` CLI reference](/docs/flyctl/machine/) Use this command‑line reference for managing Machines via `flyctl` (create, update, stop, clone, etc).
+- [Working with the Machines API](/docs/machines/api/working-with-machines-api/) Read a practical guide to creating, updating, and managing Machines via API calls.
+- [OpenAPI specification](http://docs.machines.dev/#description/introduction) Use this low-level reference for the Machines API runtime, straight from the source.
diff --git a/mcp/access-control.html.erb b/mcp/access-control.html.erb
new file mode 100644
index 0000000000..d6226cb33d
--- /dev/null
+++ b/mcp/access-control.html.erb
@@ -0,0 +1,15 @@
+---
+title: Access Control
+layout: framework_docs_overview
+toc: false
+order: 5
+---
+
+
+Making an MCP server available on the internet is a security risk. Most MCP servers are stdio and therefore not designed to be exposed to the internet.
+We recommend that you start with HTTP Authorization and only explore other options if you have specific needs.
+
+
+
+Following are some of the ways to secure your MCP server:
+
\ No newline at end of file
diff --git a/mcp/access-control/flycast.html.md b/mcp/access-control/flycast.html.md
new file mode 100644
index 0000000000..d4de775c4a
--- /dev/null
+++ b/mcp/access-control/flycast.html.md
@@ -0,0 +1,26 @@
+---
+title: Wireguard tunnels and Flycast
+layout: framework_docs
+objective: Shows how to use a wireguard tunnel and flycast to access a proxy
+status: beta
+order: 2
+---
+
+The best way not to let randos on the internet access to your MCP server is to not put the MCP server on the internet in the first place.
+
+Every Fly Organization has a [private network](https://fly.io/docs/networking/private-networking/). In most cases, you will want **only** a private v6 address on applications that are not available on the internet.
+
+When using:
+ * The Machine API, specify `private_v6`.
+ * `fly ips`, specify `allocate-v6 --private`
+ * `fly launch`, specify `--flycast`
+
+With this in place you can use [`fly proxy`](https://fly.io/docs/flyctl/proxy/) to create a tunnel, or you can follow our blueprint to [Jack into your private network with WireGuard](https://fly.io/docs/blueprints/connect-private-network-wireguard/).
+
+With `fly mcp proxy`, this support is built in. To use, simply specify a `--url` ending in `.internal` or `.flycast`.
+ * `.internal` addresses can be used to target individual machines or regions, but can only be used to access machines that are started. Just remember that the protocol to use is `http` not `https`, and the port you want to use it the _internal_ port. So an typical URL would look like `http://mcp.internal:8080/`.
+ * `.flycast` addresses target an _external_ port for your application, and supports [fly routing headers](https://fly.io/docs/networking/dynamic-request-routing/#alternative-routing-headers). If your request is routed to a machine that is stopped or suspended, that machine will be started first. Again the protocol to use is `http` not `https`, so an typical URL would look like `http://mcp.flycast/`.
+
+ [Flycast - Private Fly Proxy services](https://fly.io/docs/networking/flycast/) provides more information on the use of Flycast.
+
+ `fly mcp wrap` has a `--private` flag which will cause the proxy to respond with a `403 Forbidden` response to all requests that do not come in via the private network. This may be useful when combined with containers and machines with multiple services, some of which are public but the MCP server is private.
\ No newline at end of file
diff --git a/mcp/access-control/http-authorization.html.md b/mcp/access-control/http-authorization.html.md
new file mode 100644
index 0000000000..d4b4e9c1da
--- /dev/null
+++ b/mcp/access-control/http-authorization.html.md
@@ -0,0 +1,75 @@
+---
+title: HTTP Authorization
+layout: framework_docs
+objective: Shows how to add HTTP authorization to an MCP proxy
+status: beta
+order: 1
+---
+
+The HTTP Streaming transport [specifies](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12) for authentication. To work this needs to be implemented in both the MCP client and the MCP server. As of Spring 2025, this is not yet widely implemented.
+
+The SSE transport only [specified](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) _Implement proper authentication for all SSE connections_. As again this needs to be implemented in both the MCP client and MCP server to work, this guidance is not sufficient for interoperability. THe [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) lets you set a bearer token, and there are some who followed this lead. That being said, the SSE transport is now deprecated.
+
+For stdio transports, there is no authentication; that is left entirely up to [fly mcp proxy](https://fly.io/docs/flyctl/mcp-wrap/) and [fly mcp wrap](https://fly.io/docs/flyctl/mcp-wrap/). As these commands are designed to be used with an MCP server that was only intended to be used by a single user at a time, OAuth is substantial overkill for this purpose. Instead these commands support both [basic](https://datatracker.ietf.org/doc/html/rfc7617) and bearer [HTTP Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Authorization).
+
+To use basic authentication, set two secrets in your application. For example:
+
+```sh
+fly secrets set FLY_MCP_USER=Admin FLY_MCP_PASSWORD=S3cr3t
+```
+
+To use bearer authentication, set one secret in your application. For example:
+
+```sh
+fly secrets set FLY_MCP_BEARER_TOKEN=T0k3n
+```
+
+If you are using MacOs, Linux, or WSL2, the following command may be useful for generating a token:
+
+```sh
+openssl rand -base64 18
+```
+
+And then on the client side pass the same values to the proxy as flags:
+
+For basic:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/",
+ "--user",
+ "Admin",
+ "--password",
+ "S3cr3t"
+ ]
+ }
+ }
+}
+```
+
+For bearer:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/",
+ "--bearer-token",
+ "T0k3n"
+ ]
+ }
+ }
+}
+```
+
+From a security point of view, there is not a substantial difference between these two authentication methods. Pick the one you fine most convenient.
diff --git a/mcp/access-control/reverse-proxy.html.md b/mcp/access-control/reverse-proxy.html.md
new file mode 100644
index 0000000000..7686fbfd78
--- /dev/null
+++ b/mcp/access-control/reverse-proxy.html.md
@@ -0,0 +1,15 @@
+---
+title: Reverse Proxy
+layout: framework_docs
+objective: Shows how to use a reverse proxy to access your application
+status: beta
+order: 3
+---
+
+
+Coming soon!
+
+
+Future content to be based on:
+* [Rate Limiter Demo](https://github.com/fly-apps/rate-limiter-demo?tab=readme-ov-file#container-demo)
+* [NGINX reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
diff --git a/mcp/deploy-on.html.markerb b/mcp/deploy-on.html.markerb
new file mode 100644
index 0000000000..f52bda7946
--- /dev/null
+++ b/mcp/deploy-on.html.markerb
@@ -0,0 +1,14 @@
+---
+title: Deploy on
+layout: framework_docs_overview
+toc: false
+order: 4
+---
+
+MCP servers can run in a variety of locations.
+
+The [Docker MCP Catalog and Toolkit announcement](https://www.docker.com/blog/announcing-docker-mcp-catalog-and-toolkit-beta/) identifies a number of problems and potential solutions with running MCP servers on your laptop.
+
+Running an MCP server on a Fly Machine is easy and provides an additional level of security and isolation. It can also provide access to resources that are not on your laptop such as volumes, sqlite databases, and your application's internal state.
+
+We recommend that you start with deploying each MCP server on a separate machine and only explore other options if you have specific needs.
\ No newline at end of file
diff --git a/mcp/deploy-on/container.html.md b/mcp/deploy-on/container.html.md
new file mode 100644
index 0000000000..dccbd53a19
--- /dev/null
+++ b/mcp/deploy-on/container.html.md
@@ -0,0 +1,17 @@
+---
+title: A container
+layout: framework_docs
+objective: Demonstrates running an MCP server in a container on a fly machine
+status: beta
+order: 2
+---
+
+
+Coming soon!
+
+
+Future content to be based on:
+
+* [Rate Limiter Demo](https://github.com/fly-apps/rate-limiter-demo?tab=readme-ov-file#container-demo)
+* [Prisma React Router 7 Example](https://github.com/prisma/prisma-examples/tree/latest/orm/react-router-7#react-router-7-example)
+* [SQLite MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite#sqlite-mcp-server)
diff --git a/mcp/deploy-on/fly-machine.html.md b/mcp/deploy-on/fly-machine.html.md
new file mode 100644
index 0000000000..d5af129064
--- /dev/null
+++ b/mcp/deploy-on/fly-machine.html.md
@@ -0,0 +1,69 @@
+---
+title: A Fly Machine
+layout: framework_docs
+objective: Demonstrates using flyctl launch to create a Fly.io machine that runs an MCP server remotely.
+status: beta
+order: 1
+---
+
+[Fly Machines](https://fly.io/docs/machines/) are fast-launching VMs; they can be started and stopped at subsecond speeds. We give you control of your Machine count and each Machine’s lifecycle, resources, and region placement with a simple REST API or flyctl commands.
+
+This guide presumes that you have [flyctl installed](https://fly.io/docs/flyctl/install/), and have successfully run either
+[`fly auth signup`](https://fly.io/docs/flyctl/auth-signup/) or [`fly auth login`](https://fly.io/docs/flyctl/auth-login/).
+
+The first step is intended to be run in an empty directory.
+
+## Create your app
+
+Create a Dockerfile with the following contents:
+
+```dockerfile
+FROM flyio/mcp
+VOLUME /data
+CMD [ "npx", "-f", "@modelcontextprotocol/server-filesystem", "/data/" ]
+```
+
+Now use the [fly launch](https://fly.io/docs/flyctl/launch/) command:
+
+```sh
+fly launch
+```
+
+Review and accept the defaults.
+
+The Dockerfile will be build, and the resulting image will be pushed and deployed. In the process, a volume will be allocated and both a shared ipv4 and a dedicated ipv6 address will be allocated.
+
+## Accessing the MCP via an inspector
+
+
+ As the MCP inspector is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) first. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
+
+
+You are test out your MCP server using the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector):
+
+```sh
+fly mcp proxy -i
+```
+
+Navigate to http://127.0.0.1:6274 ; click Connect; then List Tools; select any tool; fill out the form (if any) and click Run tool.
+
+## Configure your LLM
+
+Here’s an example `claude_desktop_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/"
+ ]
+ }
+ }
+}
+```
+
+Adjust the flyctl path and the value of the --url, restart your LLM (in this case, Claude) and try out the tools.
diff --git a/mcp/deploy-on/your-app.html.md b/mcp/deploy-on/your-app.html.md
new file mode 100644
index 0000000000..9873a04222
--- /dev/null
+++ b/mcp/deploy-on/your-app.html.md
@@ -0,0 +1,42 @@
+---
+title: Your application
+layout: framework_docs
+objective: Demonstrates running an MCP server inside your application
+status: beta
+order: 3
+---
+
+[Tidewave](https://tidewave.ai/) is an example of a MCP server that runs inside your application. It currently is available for Phoenix and Rails.
+
+Phoenix installation:
+
+```elixir
+# mix.exs
+{:tidewave, "~> 0.1", only: :dev}
+
+# lib/my_app_web/endpoint.ex
+# just above the `if code_reloading? do` block
+if Code.ensure_loaded?(Tidewave) do
+ plug Tidewave
+end
+```
+
+Rails installation:
+```ruby
+gem "tidewave", group: :development
+```
+
+Example `.cursor/mcp.json`:
+
+```json
+{
+ "mcpServers": {
+ "tidewave": {
+ "url": "http://localhost:4000/tidewave/mcp"
+ }
+ }
+}
+```
+
+For Claude and others, they recommend an [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy?tab=readme-ov-file#1-stdio-to-sse).
+
diff --git a/mcp/deploy-with.html.markerb b/mcp/deploy-with.html.markerb
new file mode 100644
index 0000000000..24b6d22806
--- /dev/null
+++ b/mcp/deploy-with.html.markerb
@@ -0,0 +1,17 @@
+---
+title: Deploy with
+layout: framework_docs_overview
+toc: false
+order: 3
+---
+
+<%= youtube "https://www.youtube.com/watch?v=7zmg5PEnwR0" %>
+
+These pages show you how to deploy a stdio MCP on Fly.io using your choice of the `fly launch`, `fly machine run`, and the machines API.
+The example we use in each is the same: the [FileSystem MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) running the [flyio/mcp](https://hub.docker.com/r/flyio/mcp) image on DockerHub image on a Machine with a volume.
+
+If you are looking to deploy a MCP stdio server for yourself, `fly launch` is generally what you need.
+
+If you are looking to deploy MCP servers for your users, the Machines API gives you more knobs and control.
+
+`fly machines run` was originally intended more for experimentation and familiarizing yourself with the platform, but to our surprise it has become popular for use in scripts.
\ No newline at end of file
diff --git a/mcp/deploy-with/fly-launch.html.md b/mcp/deploy-with/fly-launch.html.md
new file mode 100644
index 0000000000..476921a090
--- /dev/null
+++ b/mcp/deploy-with/fly-launch.html.md
@@ -0,0 +1,75 @@
+---
+title: fly launch
+layout: framework_docs
+objective: This guide shows you how to use flyctl launch to create a Fly.io machine that runs an MCP server remotely.
+status: beta
+order: 1
+---
+
+[Fly launch](https://fly.io/docs/reference/fly-launch/) is a bundle of features that take a lot of the work out of deploying and managing your Fly App.
+
+This guide presumes that you have [flyctl installed](https://fly.io/docs/flyctl/install/), and have successfully run either
+[`fly auth signup`](https://fly.io/docs/flyctl/auth-signup/) or [`fly auth login`](https://fly.io/docs/flyctl/auth-login/).
+
+The first step is intended to be run in an empty directory.
+
+## Create your app
+
+Create a Dockerfile with the following contents:
+
+```dockerfile
+FROM flyio/mcp
+VOLUME /data
+CMD [ "npx", "-f", "@modelcontextprotocol/server-filesystem", "/data/" ]
+```
+
+Now use the [fly launch](https://fly.io/docs/flyctl/launch/) command:
+
+```sh
+fly launch
+```
+
+Optional parameters include `--name`, `--org`, and `--flycast`.
+
+Review and accept the defaults.
+
+The Dockerfile will be build, and the resulting image will be pushed and deployed. In the process, a volume will be allocated and both a shared ipv4 and a dedicated ipv6 address will be allocated.
+
+Your configuration will be found in [`fly.toml`](https://fly.io/docs/reference/configuration/).
+
+If you make any change to your `Dockerfile` or `fly.toml`, run [`fly deploy`](https://fly.io/docs/flyctl/deploy/) to apply the changes.
+
+## Accessing the MCP via an inspector
+
+
+ As the MCP inspector is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) first. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
+
+
+You are test out your MCP server using the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector):
+
+```sh
+fly mcp proxy -i
+```
+
+Navigate to http://127.0.0.1:6274 ; click Connect; then List Tools; select any tool; fill out the form (if any) and click Run tool.
+
+## Configure your LLM
+
+Here’s an example `claude_desktop_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/"
+ ]
+ }
+ }
+}
+```
+
+Adjust the flyctl path and the value of the --url, restart your LLM (in this case, Claude) and try out the tools.
diff --git a/mcp/deploy-with/flyctl-machines.html.md b/mcp/deploy-with/flyctl-machines.html.md
new file mode 100644
index 0000000000..353507b190
--- /dev/null
+++ b/mcp/deploy-with/flyctl-machines.html.md
@@ -0,0 +1,116 @@
+---
+title: fly machines run
+layout: framework_docs
+objective: This guide shows you how to use flyctl commands to create a Fly.io machine that runs an MCP server remotely.
+status: beta
+order: 3
+---
+
+The [Fly.io command line interface](https://fly.io/docs/flyctl/help/) is a higher level interface that enables you to do much of what you can do via the Machines API. It is suitable for scripting and ad hoc exploration and updates.
+
+This guide presumes that you have [flyctl installed](https://fly.io/docs/flyctl/install/), and have successfully run either
+[`fly auth signup`](https://fly.io/docs/flyctl/auth-signup/) or [`fly auth login`](https://fly.io/docs/flyctl/auth-login/).
+
+The first step is intended to be run in an empty directory.
+
+## Create your app
+
+Now use the [fly apps create](https://fly.io/docs/flyctl/apps-create/) command:
+
+```sh
+fly apps create --generate-name --save
+```
+
+If you prefer, you can replace `--generate-name` with a name of your choice.
+
+You can also specify the organization by passing in a `--org` pparameter, or let it prompt you.
+
+## Create IP addresses for your application
+
+The following will create a shared IPv4 address and a dedicated IPv6 address:
+
+```sh
+fly ips allocate-v4 --shared
+fly ips allocate-v6
+```
+
+If your application is going to be public, but instead is only going to be accessed by other applications within your organization, run the following instead:
+
+```sh
+fly ips allocate-v6 --private
+```
+
+## Create a volume
+
+This demo uses a volume. If your application doesn't use a volume skip this step.
+
+```sh
+fly volumes create data --region iad --yes
+```
+
+Adjust the [region](https://fly.io/docs/reference/regions/#fly-io-regions) as necessary.
+
+## Create a machine
+
+This next part contains a lot of [flags](https://fly.io/docs/flyctl/machine-run/), so first an overview:
+
+* The first parameter specifies the image we will be running. Fly.io provides an image capable of running `npx` and `uvx`, which is sufficient to run many MCPs. If you have a custom MCP with unique requirements, you can provide your own image.
+* The next parameter specifies the command we will be running.
+* `--entrypoint` invokes `fly mcp wrap` passing the command we specified.
+* If you are using a volume, the `--region` selected must match a region in which you have an allocated but unattached volume.
+* `--volume` specifies the volume, and where it is to be mounted.
+* The `--vm-*` parameters specify the size of the machine desired.
+* `--auto*` and `--port` define what network services your application provides.
+
+```sh
+fly machine run flyio/mcp:latest \
+ "npx -f @modelcontextprotocol/server-filesystem /data/" \
+ --entrypoint "/usr/bin/flyctl mcp wrap --" \
+ --region iad --volume data:/data \
+ --vm-cpu-kind shared --vm-cpus 1 --vm-memory 1024 \
+ --autostart=true --autostop=stop \
+ --port 80:8080/tcp:http --port 443:8080/tcp:http:tls
+```
+
+Note that this creates and starts a machine. If you want to only create the machine, use [`fly machine create`](https://fly.io/docs/flyctl/machine-create/) instead.
+
+Once this command completes, you can update your `fly.toml` to include this new information using the following command:
+
+```sh
+fly config save --yes
+```
+
+## Accessing the MCP via an inspector
+
+
+ As the MCP inspector is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) first. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
+
+
+You are test out your MCP server using the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector):
+
+```sh
+fly mcp proxy -i
+```
+
+Navigate to http://127.0.0.1:6274 ; click Connect; then List Tools; select any tool; fill out the form (if any) and click Run tool.
+
+## Configure your LLM
+
+Here’s an example `claude_desktop_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/"
+ ]
+ }
+ }
+}
+```
+
+Adjust the flyctl path and the value of the --url, restart your LLM (in this case, Claude) and try out the tools.
diff --git a/mcp/deploy-with/machines-api.html.md b/mcp/deploy-with/machines-api.html.md
new file mode 100644
index 0000000000..15a9455ff9
--- /dev/null
+++ b/mcp/deploy-with/machines-api.html.md
@@ -0,0 +1,240 @@
+---
+title: Machines API
+layout: framework_docs
+objective: This guide shows you how to use the Machines API to create a Fly.io machine that runs an MCP server remotely.
+status: beta
+order: 2
+---
+
+The [Machines API](https://fly.io/docs/machines/api/) is a low level interface that provides access to the full range of platform services.
+It consists of a set of REST and a GraphQL interfaces. You can access these from any programming language
+capable of producing HTTP GET and POST requests and the ability to generate and parse JSON.
+
+An example of when you would want to use the Machines API is when you want to create a [Per-User Dev Environment](https://fly.io/docs/blueprints/per-user-dev-environments/)
+
+This guide presumes that you are running MacOS, Linux, or WSL2, and have [curl](https://curl.se/) installed.
+
+## Select an organization
+
+All Fly.io users have a personal organization and may be a member of other organizations. Set an environment variable with your choice.
+
+```sh
+export ORG="personal"
+```
+
+## Organization token
+
+To get started, you need an organization token, which you can obtain via the dashboard.
+
+Go to https://fly.io/dashboard, change the organization if necessary, select Tokens from the list on the left hand side of the page, optionally enter a token name or an expiration period and click on Create Organization token. Once complete you should see something like the following:
+
+
+
+Click on Copy to clipboard, and then run the following command with your token pasted inside the quotes:
+
+```sh
+export FLY_API_TOKEN="FlyV1 fm2_IJPECAAA..."
+```
+
+## Select an API hostname
+
+The host name you chose depends on whether your application is running [inside or outside your Fly.io private Wireguard network](https://fly.io/docs/machines/api/working-with-machines-api/#api-addresses). If you are not sure, use the public base URL:
+
+```sh
+export FLY_API_HOSTNAME=https://api.machines.dev
+```
+
+## Chose a hostname for your app
+
+You are free to chose any available hostname for your application. The following will generate a name that is likely to be unique:
+
+```sh
+export APP_NAME=mcp-demo-$(uuidgen | cut -d '-' -f 5 | tr A-Z a-z)
+```
+
+## Create your app
+
+Now use the [Create a Fly App](https://fly.io/docs/machines/api/apps-resource/#create-a-fly-app) API:
+
+```json
+curl -i -X POST \
+ -H "Authorization: Bearer ${FLY_API_TOKEN}" \
+ -H "Content-Type: application/json" \
+ "${FLY_API_HOSTNAME}/v1/apps" \
+ -d "{ \
+ \"app_name\": \"${APP_NAME}\", \
+ \"org_slug\": \"${ORG}\" \
+ }"
+```
+
+Response should be a `200` Success, along with some JSON output.
+
+## Create IP addresses for your application
+
+
+ While the Fly.io GraphQL endpoint is public, our [usage of it](https://github.com/superfly/fly-go/blob/main/resource_ip_addresses.go) is [open source](https://github.com/superfly/fly-go/blob/main/LICENSE), can be directly observed by setting `LOG_LEVEL=debug` before running `flyctl` commands, and there are no current plans to change it, be aware that this interface is not guaranteed to be stable and can change without notice. If this is a concern, consider using the [`fly ips`](https://fly.io/docs/flyctl/ips/) command instead.
+
+
+The following will create a shared IPv4 address and a dedicated IPv6 address:
+
+```json
+curl -i -X POST \
+ -H "Authorization: Bearer ${FLY_API_TOKEN}" \
+ -H "Content-Type: application/json" \
+ "https://api.fly.io/graphql" \
+ -d @- <
+
+
+
+## Adding fly mcp server to your LLM
+
+```
+fly mcp server --claude
+```
+
+You can also specify `--cursor`, `--neovim`, `--vscode`, `--windsurf`, or `--zed`. Or specify a configuration file path directly using `--config`.
+
+`flyctl` provides an MCP server that you can use to provision your application. At the present time, most of the following commands and their subcommands are supported:
+
+* [apps](https://fly.io/docs/flyctl/apps/) - Manage Fly applications. A Fly App is an abstraction for a group of Fly Machines running your code on Fly.io.
+* [certs](https://fly.io/docs/flyctl/certs/) - Manage the certificates associated with a deployed application.
+* [logs](https://fly.io/docs/flyctl/logs/) - View application logs as generated by the application running on the Fly platform.
+* [machine](https://fly.io/docs/flyctl/machine/) - Manage Fly Machines. Fly Machines are super-fast, lightweight VMs that can be created, and then quickly started and stopped as needed with flyctl commands or with the Machines REST fly.
+* [orgs](https://fly.io/docs/flyctl/orgs/) - Manage Fly organizations. Organization admins can also invite or remove users from organizations.
+* [platform](https://fly.io/docs/flyctl/platform/) - Information about the Fly platform
+* [secrets](https://fly.io/docs/flyctl/secrets/) - Manage secrets. Secrets are provided to applications at runtime as ENV variables.
+* [status](https://fly.io/docs/flyctl/status/) - Show the application’s current status including application details, tasks, most recent deployment details and in which regions it is currently allocated.
+* [volumes](https://fly.io/docs/flyctl/volumes/) - Manage Fly Volumes. Volumes are persistent storage for Fly Machines.
+
+## Running with the MCP inspector
+
+You can explore the `flyctl mcp server` using the MCP inspector:
+
+
+ As the MCP inspector is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) first. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
+
+
+```sh
+fly mcp server -i
+```
+
+Navigate to http://127.0.0.1:6274 ; click Connect; then List Tools; then a tool like `fly-platform-status`, `fly-orgs-list`, `fly-apps-list`, or `fly-machines-list`; then fill out the form (if any) and click Run tool.
+
+## Running on a separate machine
+
+
+Running this server remotely can give others access to run commands on your behalf. Read the following carefully before proceeding.
+
+
+Both `--sse` and `-stream` options are supported.
+
+The default bind address is `127.0.0.1` which will only allow requests from the same machine. to override specify `--bind-addr`.
+
+Authentication tokens come from (in priority order):
+
+ * `bearer-token` from the `Authentication` header on the request
+ * `--access-token` flag on the `fly mcp server` command
+ * `FLY_ACCESS_TOKEN` environment variable
+
+See [Access Tokens](https://fly.io/docs/security/tokens/) for information on how to obtain a token.
+
diff --git a/mcp/index.html.markerb b/mcp/index.html.markerb
new file mode 100644
index 0000000000..5fe7eee8db
--- /dev/null
+++ b/mcp/index.html.markerb
@@ -0,0 +1,27 @@
+---
+title: Model Context Protocol
+layout: framework_docs
+toc: false
+---
+
+<%= youtube "https://www.youtube.com/watch?v=74c1ByGvFPE" %>
+
+
+Fly.io is a great place to run MCP servers. We also provide an [MCP server](./flyctl-server) that you can use to provision your application.
+
+
+Anthropic [announced](https://www.anthropic.com/news/model-context-protocol) the [Model Context Protocol](https://modelcontextprotocol.io/introduction) on November 25, 2024, along with a number of [SDKs](https://modelcontextprotocol.io/sdk/java/mcp-overview) in a variety of languages.
+There also is a large list of [existing servers](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#model-context-protocol-servers) that you can use.
+
+Depoloying an `npx`, `uv`, `go run`, or docker image stdio MCP server into a Fly machine and configuring a MCP client to connect to it is made easy by [`fly mcp launch`](./launch). See [examples](./examples).
+
+Deploying other MCP servers involves making four choices: [MCP transport](./transports), the interface you use to [deploy it with](./deploy-with), where you want to [deploy it on](./deploy-on), and [access control](./access-control). In each case we present your choices, starting with our recommendation.
+
+---
+
+General considerations that apply across these pages:
+
+ * Most guides presume that you have [flyctl installed](https://fly.io/docs/flyctl/install/), and have successfully run either
+[`fly auth signup`](https://fly.io/docs/flyctl/auth-signup/) or [`fly auth login`](https://fly.io/docs/flyctl/auth-login/).
+ * As the MCP protocol is both asynchronous and stateful, these examples will be run on a single machine (also known as a **non** highly available configuration). This enables MCP clients to fetch replies from the same server that they sent the request to.
+ * As the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) before you use it. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
diff --git a/mcp/launch.html.md b/mcp/launch.html.md
new file mode 100644
index 0000000000..b71ad13e3b
--- /dev/null
+++ b/mcp/launch.html.md
@@ -0,0 +1,48 @@
+---
+title: fly mcp launch
+layout: framework_docs_overview
+toc: false
+order: 1
+---
+
+Launching an `npx`, `uvx`, `go run` or docker image stdio MCP server into a Fly machine and configuring a MCP client to connect to it is a one-step process. The `fly mcp launch` command will create a new Fly machine, install the MCP server, and configure the MCP client to connect to it.
+
+```sh
+fly mcp launch "uvx mcp-server-time" --claude --server time
+```
+
+The above command specifies the command to run in the machine, selects the `claude` client to be the one to be configured using the server name `time`.
+
+Support for Claude, Cursor, Neovim, VS Code, Windsurf, and Zed are built in. You can also provide the path to the configuration file. You can also provide multiple clients and configuration files at once.
+
+By default, bearer token authentication will be set up on both the server and client, though there are other options and this can be disabled.
+
+You can configure auto-stop, file contents, flycast, secrets, region, and vm sizes.
+
+See [Examples](../examples/) for a number of examples.
+
+See the [`fly mcp launch` documentation](https://fly.io/docs/flyctl/mcp-launch/) for more details on the command and its options.
+
+## Inspect
+
+You can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) to test and debug your MCP server:
+
+
+ As the MCP inspector is a Node.js application, you need to [Download and install Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) first. MacOS users can use [`brew install node`](https://formulae.brew.sh/formula/node).
+
+
+```sh
+fly mcp inspect --claude --server time
+```
+
+This command is simply a convenience, all it does is run the inspector set up to connect to the same machine, authentication, and arguments as the MCP client (in this case, Claude) would.
+
+## Destroy
+
+When you no longer need the MCP, you can destroy it:
+
+```sh
+fly mcp destroy --claude --server time
+```
+
+This will also remove the configuration entry from the MCP client.
\ No newline at end of file
diff --git a/mcp/transports.html.md b/mcp/transports.html.md
new file mode 100644
index 0000000000..3ac8b6b41d
--- /dev/null
+++ b/mcp/transports.html.md
@@ -0,0 +1,22 @@
+---
+title: MCP Transports
+layout: framework_docs_overview
+toc: false
+order: 2
+---
+
+The MCP standard define two types of base transports:
+[stdio](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) and
+[Streaming HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http).
+Previously there was also a
+[Server Sent Events (SSE)](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) transport; this is now deprecated but still supported by a number of tools.
+
+At the present time, MCPs that implement the stdio transport are by far most common, most interoperable, and is the transport we recommend.
+
+Below are instructions on how to deploy each of these transport mechanisms on Fly.io using
+the [Everything MCP Server](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/README.md) provided by Anthropic which
+_attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test server for builders of MCP clients. It implements prompts, tools, resources, sampling, and more to showcase MCP capabilities._. It currently supports stdio, SSE, and Streaming HTTP.
+
+
+
+One thing worth trying independent of the transport method you chose is the `printEnv` tool. From the inspector click on tools at the top, then List Tools, then printEnv, and finally Run Tool.
\ No newline at end of file
diff --git a/mcp/transports/sse.html.md b/mcp/transports/sse.html.md
new file mode 100644
index 0000000000..4994b52765
--- /dev/null
+++ b/mcp/transports/sse.html.md
@@ -0,0 +1,73 @@
+---
+title: SSE
+layout: framework_docs
+objective: SSE servers can be deployed as is.
+status: beta
+order: 2
+---
+
+SSE servers can be run as is, we just need to identify the port used and adjust the command, and disable the smoke checks as they will confuse the server.
+
+Start by cloning the MCP servers git repository and making a copy of the `Dockerfile`:
+
+```sh
+git clone https://github.com/modelcontextprotocol/servers.git
+cd servers
+cp src/everything/Dockerfile .
+```
+
+Make the following changes to the `Dockerfile`:
+
+```diff
+ RUN npm ci --ignore-scripts --omit-dev
++
++EXPOSE 3001
+
+-CMD ["node", "dist/index.js"]
++CMD ["node", "dist/sse.js"]
+```
+
+Now run:
+
+```sh
+fly launch --ha=false --smoke-checks=false
+```
+
+Access the MCP server via the MCP inspector:
+
+```sh
+npx @modelcontextprotocol/inspector
+```
+
+In the top left, change the transport type to **SSE**.
+
+Set the URL to match your application, where the URL will be of the form:
+
+```
+https://appname.fly.dev/sse
+```
+
+Replace the _appname_ with the name of your application, but keep the `https://` and `/sse`.
+
+Click Connect.
+
+An example Cursor configuration:
+
+```json
+{
+ "mcpServers": {
+ "everything": {
+ "url": "https://appname.fly.dev/sse",
+ "env": {}
+ }
+ }
+}
+```
+
+With SSE transports, [`fly mcp proxy`](https://fly.io/docs/flyctl/mcp-proxy/) may not be required, but could be useful if:
+* Your MCP client doesn't support SSE
+* You need to set up a [wireguard tunnel](https://fly.io/docs/flyctl/proxy/) to access your MCP serever via an [`.internal`](https://fly.io/docs/networking/private-networking/) or [`.proxy`](https://fly.io/docs/networking/flycast/) address.
+* You need to [route the request to a specific machine](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-force-instance-id-header).
+
+
+
diff --git a/mcp/transports/stdio.html.md b/mcp/transports/stdio.html.md
new file mode 100644
index 0000000000..6ba33cf842
--- /dev/null
+++ b/mcp/transports/stdio.html.md
@@ -0,0 +1,64 @@
+---
+title: stdio
+layout: framework_docs
+objective: This guide shows you how to wrap and proxy a stdio MCP server so that it can be deployed remotely.
+status: beta
+order: 1
+---
+
+stdio MCP servers are not intended to be run remotely, but [`fly mcp`](https://fly.io/docs/flyctl/mcp/) provides the ability to proxy and wrap them.
+
+The data flow is tthat the proxy is a stdio MCP that forwards requests to a wrapper MCP (basically a slimmed down and streamlined Streamable HTTP server), which in turn forwards requests to a stdio MCP running on a remote server:
+
+
+
+Start by cloning the MCP servers git repository and making a copy of the `Dockerfile`:
+
+```sh
+git clone https://github.com/modelcontextprotocol/servers.git
+cd servers
+cp src/everything/Dockerfile .
+```
+
+Make the following changes to the `Dockerfile`:
+
+```diff
+ RUN npm ci --ignore-scripts --omit-dev
++
++COPY --from=flyio/flyctl /flyctl /usr/bin
++ENTRYPOINT [ "/usr/bin/flyctl", "mcp", "wrap", "--" ]
++EXPOSE 8080
+
+ CMD ["node", "dist/index.js"]
+```
+
+Now run:
+
+```sh
+fly launch --ha=false
+```
+
+Access the MCP server via the MCP inspector:
+
+```sh
+fly mcp proxy -i
+```
+
+An example `claude_desktop_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "filesystem": {
+ "command": "/Users/rubys/.fly/bin/flyctl",
+ "args": [
+ "mcp",
+ "proxy",
+ "--url=https://mcp.fly.dev/"
+ ]
+ }
+ }
+}
+```
+
+Adjust the `flyctl` path and the value of the `--url` to match your needs.
diff --git a/mcp/transports/streaming-http.html.md b/mcp/transports/streaming-http.html.md
new file mode 100644
index 0000000000..9249970a8f
--- /dev/null
+++ b/mcp/transports/streaming-http.html.md
@@ -0,0 +1,70 @@
+---
+title: Streaming HTTP
+layout: framework_docs
+objective: Streaming HTTP servers can be deployed as is.
+status: beta
+order: 3
+---
+
+Streaming HTTP servers can be run as is, we just need to identify the port used and adjust the command, and disable the smoke checks as they will confuse the server.
+
+Start by cloning the MCP servers git repository and making a copy of the `Dockerfile`:
+
+```sh
+git clone https://github.com/modelcontextprotocol/servers.git
+cd servers
+cp src/everything/Dockerfile .
+```
+
+Make the following changes to the `Dockerfile`:
+
+```diff
+ RUN npm ci --ignore-scripts --omit-dev
++
++EXPOSE 3001
+
+-CMD ["node", "dist/index.js"]
++CMD ["node", "dist/streamableHttp.js"]
+```
+
+Now run:
+
+```sh
+fly launch --ha=false --smoke-checks=false
+```
+
+Access the MCP server via the MCP inspector:
+
+```sh
+npx @modelcontextprotocol/inspector
+```
+
+In the top left, change the transport type to **Streamable HTTP**.
+
+Set the URL to match your application, where the URL will be of the form:
+
+```
+https://appname.fly.dev/mcp
+```
+
+Replace the _appname_ with the name of your application, but keep the `https://` and `/mcp`.
+
+Click Connect.
+
+An example Cursor configuration:
+
+```json
+{
+ "mcpServers": {
+ "everything": {
+ "url": "https://appname.fly.dev/mcp",
+ "env": {}
+ }
+ }
+}
+```
+
+With Streaming HTTP transports, [`fly mcp proxy`](https://fly.io/docs/flyctl/mcp-proxy/) may not be required, but could be useful if:
+* Your MCP client doesn't support Streaming HTTP
+* You need to set up a [wireguard tunnel](https://fly.io/docs/flyctl/proxy/) to access your MCP serever via an [`.internal`](https://fly.io/docs/networking/private-networking/) or [`.proxy`](https://fly.io/docs/networking/flycast/) address.
+* You need to [route the request to a specific machine](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-force-instance-id-header).
\ No newline at end of file
diff --git a/monitoring/error-codes.html.md b/monitoring/error-codes.html.md
index e0f2f87101..d850f8ea38 100644
--- a/monitoring/error-codes.html.md
+++ b/monitoring/error-codes.html.md
@@ -2,7 +2,7 @@
title: Error codes and troubleshooting
layout: docs
nav: firecracker
-redirect_from:
+redirect_from:
- /docs/reference/error-codes/
- /docs/metrics-and-logs/error-codes/
---
@@ -189,9 +189,9 @@ If a request grows larger than 10MB, buffering stops, making it impossible to re
After a ['fly-replay' response header](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header) replays a request, the application may respond normally, or it may issue another `fly-replay`, up to 10 times. `PA02` is emitted when `fly-replay` is returned more than 10 times.
-#### PA03: Malformed fly-replay header
+#### PA03: Invalid fly-replay
-Your app returned a malformed ['fly-replay' response header](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header).
+Your app returned an invalid ['fly-replay' response](https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header).
#### PA04: Replay target app not found
@@ -275,4 +275,4 @@ To diagnose, check if:
#### PL02: Bypassed request concurrency limit
-This error is similar to `PL01`, but refers to concurrency measured as the number of concurrent **requests**.
\ No newline at end of file
+This error is similar to `PL01`, but refers to concurrency measured as the number of concurrent **requests**.
diff --git a/monitoring/index.html.markerb b/monitoring/index.html.markerb
index e3166a3bc0..c550e8fc43 100644
--- a/monitoring/index.html.markerb
+++ b/monitoring/index.html.markerb
@@ -12,8 +12,6 @@ redirect_from:
-**Important:** Support for this feature is limited and best-effort. Use with caution.
-
## Metrics
_Fully-managed metrics solutions to help you monitor your apps._
@@ -32,9 +30,10 @@ _Error monitoring for your apps from our extension providers._
## Logging
-_Live tail and search your app’s logs, or ship them where you want them._
+_Live tail and search your app’s logs, access them via API, or ship them where you want them._
- **[Logging overview](/docs/monitoring/logging-overview/):** How logs work on Fly.io.
+- **[Logs API options for programmatic access](/docs/monitoring/logs-api-options/)::** How to fetch, stream or export logs programmatically without touching the CLI.
- **[Live tail logs](/docs/monitoring/live-tail-logs/):** Where to access live tail logs.
- **[Search logs](/docs/monitoring/search-logs/):** Searchable app logs in Grafana (beta).
- **[Export logs](/docs/monitoring/exporting-logs/):** Aggregate your logs to a service of your choice.
diff --git a/monitoring/logging-overview.html.markerb b/monitoring/logging-overview.html.markerb
index 6792da4469..7f23e7922a 100644
--- a/monitoring/logging-overview.html.markerb
+++ b/monitoring/logging-overview.html.markerb
@@ -19,6 +19,10 @@ Vector can act as a NATS client, read the logs, and ship them somewhere. An exam
You can live tail logs from the dashboard or with the `fly logs` command. For more information, see [Live tail logs](/docs/monitoring/live-tail-logs/).
+## Logs API options
+
+Learn how to fetch, stream or export logs programmatically without touching the CLI. For more information, see [Logs API options](/docs/monitoring/logs-api-options/).
+
## Search logs
We have an app log search feature in Grafana that retains logs for 30 days. For more information, see [Search logs](/docs/monitoring/search-logs/).
diff --git a/monitoring/logs-api-options.html.md b/monitoring/logs-api-options.html.md
new file mode 100644
index 0000000000..ffd52124c5
--- /dev/null
+++ b/monitoring/logs-api-options.html.md
@@ -0,0 +1,117 @@
+---
+title: Logs API options for programmatic access
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-10-01
+---
+
+## Overview
+
+Fly.io apps run on Firecracker microVMs we call Machines. Each Machine captures `stdout`/`stderr`, ships those logs over NATS, and stores them for a period of time in a Quickwit-backed search index. Most users consume logs via `fly logs` or by setting up log shipping to an external sink.
+
+But sometimes you want to grab logs directly, without a CLI or setting up an exporter, because you’re building a tool, automating something, or you just want to pipe logs into your own system.
+
+---
+
+## Options for fetching logs programmatically
+
+You can get logs programmatically via:
+
+- **Undocumented HTTP API**: What `fly logs` uses under the hood.
+- **NATS proxy**: Subscribe to real-time logs at the message level.
+- **Log shipper to external sink**: Ingest logs elsewhere, then query them via that system.
+
+Each has tradeoffs. If you want durable search, export logs. If you want low-latency push, use NATS. If you want to `curl` something now, use the HTTP API.
+
+### 1. HTTP API (same as `fly logs`)
+
+The Fly CLI hits an internal API to stream logs. You can use it too:
+
+```
+GET /api/v1/apps/:app_name/logs
+Authorization:
+```
+
+You’ll get a stream of newline-delimited JSON log lines. You can pass query params like:
+
+- `region=cdg` (filter by region)
+- `instance=` (filter by instance — sometimes flaky)
+- `start_time=2023-08-01T00:00:00Z` (to backfill)
+
+This endpoint isn’t officially documented, but it’s mostly stable: `flyctl` depends on it. That said, filters don’t always work as expected.
+
+Use this for quick fetches or simple polling scripts. If you hit rate limits or auth issues, check that your token has `read` access to the app.
+
+Importantly, this is the only option that gives you access to historical logs going back to the current retention window (about 15 days). The other options only start streaming from the moment they're connected.
+
+### 2. Subscribe to logs via NATS (experimental)
+
+Logs are streamed through NATS under subjects like:
+
+```
+logs...
+```
+
+You can subscribe directly using NATS clients, connecting via the proxy URL and authenticating with your org slug and personal access token.
+
+Example NATS subject:
+
+```
+logs.my-app.lhr.*
+```
+
+This gives you structured JSON log messages in real time.
+
+
+**Note:** The NATS proxy is a dusty corner of Fly’s platform. It works, but it’s unofficial, experimental, and may change as we evolve our logging infrastructure. If you’re building something serious, we recommend exporting logs instead.
+
+
+**Things to know:**
+
+- You’ll need to handle reconnections: if the network drops or the proxy restarts, your subscriber needs to reconnect and resume without losing messages.
+- You’ll need to handle backpressure: if your app can’t process logs fast enough, messages might pile up and get dropped. Design your consumer to either keep up or fail gracefully.
+- If you want to dedupe across subscribers, use NATS queue groups.
+- NATS only streams logs from starting from the moment you connect. You won’t get any history unless you’ve been subscribed the whole time.
+
+For more details on connecting to Fly’s NATS log stream (authentication, subject patterns, example clients), head over to the [Observability for User Apps](/docs/blueprints/observability-for-user-apps/?utm_source=chatgpt.com#streaming-fly-app-logs-to-your-end-users) guide.
+
+### 3. Log Shipper to External Sink
+
+If you want durable, queryable logs, export them. We maintain a small app called the [Fly Log Shipper](https://github.com/superfly/fly-log-shipper) that listens to NATS and forwards logs to sinks like Loki, Datadog, Elastic, Honeycomb, or even S3.
+
+The log shipper is a prebuilt Fly app running Vector, with configuration you can customize via TOML. It subscribes to log subjects and streams data wherever you want.
+
+Example config:
+
+```
+[sinks.loki]
+type = "loki"
+inputs = ["fly_logs"]
+endpoint = "https://my-loki-instance.example/"
+encoding.codec = "json"
+```
+
+To control which logs get forwarded, set the `SUBJECT` environment variable when deploying:
+
+```
+SUBJECT="logs.my-app.*.*"
+```
+
+Like NATS, the log shipper only sees logs from the moment it connects onward. If you deploy it today, you won’t see logs from yesterday.
+
+The log shipper is the most robust option if you want long-term retention, full-text search, alerting, or integration with your existing observability stack.
+
+More details:
+
+- [Fly Docs: Export logs](/docs/monitoring/exporting-logs/)
+- [Fly Blog: Shipping Logs](https://fly.io/blog/shipping-logs/)
+- [GitHub: superfly/fly-log-shipper](https://github.com/superfly/fly-log-shipper)
+
+---
+
+## Summary: So what should I use?
+
+- Want to grep logs from a script? Try the HTTP API.
+- Want real-time stream into your own system? NATS (with caveats).
+- Want reliable search and dashboards? Export logs via shipper.
diff --git a/monitoring/metrics.html.md b/monitoring/metrics.html.md
index ca2ad50d00..2e9aad499c 100644
--- a/monitoring/metrics.html.md
+++ b/monitoring/metrics.html.md
@@ -60,7 +60,8 @@ Queries can be sent to the following endpoint:
https://api.fly.io/prometheus//
```
-You'll need to authenticate with a Fly Access Token sent in the [standard](https://www.rfc-editor.org/rfc/rfc6750.html) Bearer Token format (e.g., an HTTP request header `Authorization: Bearer `), and you may only query series scoped to your organizations.
+You’ll need to authenticate with a [Fly Access Token](https://fly.io/docs/security/tokens/). Depending on the token type, the header uses either the Bearer format (`Authorization: Bearer `) or the FlyV1 format (`Authorization: FlyV1 `). You may only query series scoped to your organizations.
+
#### Manually
@@ -117,7 +118,9 @@ You can also configure your Prometheus endpoint with an existing Grafana install
2. Fill the form with the following:
- HTTP -> URL: `https://api.fly.io/prometheus//`
- Custom HTTP Headers -> + Add Header:
- - Header: `Authorization`, Value: `Bearer `
+ - Header: `Authorization`
+ - Value: `FlyV1 ` (for tokens created with `fly tokens create`)
+ - Value: `Bearer ` (only for tokens from `flyctl auth token`)
You're all set.
@@ -186,6 +189,14 @@ Derived from the [`/proc` file system](https://www.kernel.org/doc/html/latest/fi
`fly_instance_up = 1` shows the VM is reporting correctly.
+### Instance exit - `fly_instance_exit_`
+
+Information about instance exits. These metrics help you understand why your instances are terminating and can be used for alerting and debugging.
+
+- `fly_instance_exit_code`: The exit code of the main process when the instance terminates. A value of 0 typically indicates normal termination, while non-zero values indicate errors or abnormal termination.
+- `fly_instance_exit_oom`: A boolean flag (0 or 1) indicating whether the instance was killed due to out-of-memory (OOM). A value of 1 means the instance was terminated because it exceeded its memory limits.
+- `fly_instance_exit_vm_code`: The VM-level exit code, which may differ from the application exit code. This can help distinguish between application-level failures and VM-level issues.
+
#### Instance memory - `fly_instance_memory_`
Derived from [`/proc/meminfo`](https://www.kernel.org/doc/html/latest/filesystems/proc.html#meminfo). All units are in *bytes*.
@@ -416,21 +427,24 @@ This kind of token or "[macaroon](https://fly.io/blog/macaroons-escalated-quickl
Create an org-restricted token:
```
-fly token create org -o THE_ORGANIZATION
+fly tokens create org -o THE_ORGANIZATION
```
Create a read-only org-restricted token:
```
-fly token create readonly
+fly tokens create readonly
```
These tokens look like this once generated: `FlyV1 fm2_lJPECAAAAAAAAC7txBAzYI6PRWhHLT...(a lot of base64-encoded text)`.
#### Using tokens
-To use one of these tokens in an HTTP header, the "FlyV1" identifier replaces the "Bearer" token identifier. So it would look like this:
+Use the correct scheme based on token type:
+- `Bearer ` → for `flyctl auth token`.
+- `FlyV1 ` → for all tokens created with `fly tokens create`.
+Example with a token created via `fly tokens create`:
```
Authorization: FlyV1 fm2_lJPECAAAAAAAAC7txBAzYI6PRWhHLT...(a lot of base64-encoded text)
```
diff --git a/monitoring/sentry.html.md b/monitoring/sentry.html.md
index 4b2506ae13..40cf7e6d1c 100644
--- a/monitoring/sentry.html.md
+++ b/monitoring/sentry.html.md
@@ -3,61 +3,95 @@ title: Application Monitoring by Sentry
layout: docs
nav: firecracker
redirect_from: /docs/reference/sentry/
+date: 2025-07-21
---
-[Sentry](https://sentry.io) is a developer-first application monitoring platform that helps you identify and fix software problems before they impact your users. Through our partnerships with Sentry, each of your Fly organizations can claim a year's worth of [Team Plan](https://sentry.io/pricing) credits.
+[Sentry](https://sentry.io/) is a developer-first application monitoring platform that helps you identify and fix software problems before they impact your users. Through our partnership with Sentry, each of your Fly organizations can claim a year’s worth of [Team Plan](https://sentry.io/pricing) credits.
-To configure your application to use Sentry, run this command from your Fly.io application directory.
+## Set up Sentry for your Fly.io app
-```cmd
-flyctl ext sentry create
+In your project source directory, run the following command:
+
+```
+fly ext sentry create
```
-Use this command to open the dashboard for the Sentry project associated with the current application.
+This command will:
-```cmd
-flyctl ext sentry dashboard
-```
+- Create a Sentry account using your Fly.io user email
+- Create a Sentry organization linked to your Fly.io organization
+- Set the `SENTRY_DSN` secret in your app
-This will:
+Once this is complete, your app will have the `SENTRY_DSN` environment variable available at runtime. Most Sentry SDKs will automatically pick this up and begin sending events.
-* Create a Sentry account using your Fly.io email address
-* Create a Sentry organization mapped to your Fly.io organization
-* Set `SENTRY_DSN` as a secret on your app
+You can open the Sentry dashboard for your app by running:
+```
+fly ext sentry dashboard
+```
-`SENTRY_DSN` is available as an environment variable. The Sentry SDK will detect and use it automatically.
+## Instrument your application
+To start sending events to Sentry, you’ll need to instrument your app with the relevant SDK. See [Sentry's documentation for supported platforms](https://docs.sentry.io/platforms/).
## Sentry Plan details
-This promotional plan includes, monthly:
+Your organization receives one year of Sentry’s Team plan, which includes monthly:
+
+- 50k errors
+- 100k [performance units](https://sentry.io/changelog/2023-5-9-introducing-performance-units/)
+- 500 [session replays](https://sentry.zendesk.com/hc/en-us/articles/27282849806235-How-are-Replays-charged-Is-it-based-on-when-I-hit-play)
+- 1GB [attachments](https://docs.sentry.io/platforms/native/guides/minidumps/enriching-events/attachments/)
+
+If you need more than these allowances, sign in to your Sentry account and review upgrade options in the **Settings > Usage & Billing > Subscription** section.
+
+
+If you upgrade to a paid plan, you will not be able to return to the Fly.io‑sponsored promotional plan.
+
+
+## After the one-year plan ends
+
+At the end of the promotional period, your Sentry organization will no longer have access to the sponsored Team plan. You have two options:
+
+### 1. Let the plan expire
+
+- Your organization will be downgraded to the free Developer plan
+- Monitoring will continue, but with lower plan limits
+- Events sent that exceed the quota will be dropped
+- Existing events and logs remain accessible for up to 30 days, based on Sentry's data retention policy
+
+For more information, see [https://sentry.zendesk.com/hc/en-us/articles/27118913621019](https://sentry.zendesk.com/hc/en-us/articles/27118913621019).
+
+### 2. Upgrade to a paid Sentry plan
+
+- You can maintain access to higher quotas and additional features
+- You can enable new SSO options (Google, GitHub, etc.)
+- You can optionally disable Fly.io SSO
-* 50k errors
-* 100k [performance units](https://docs.sentry.io/product/performance/transaction-summary/?original_referrer=https%3A%2F%2Fduckduckgo.com%2F#what-is-a-transaction)
-* 500 [session replays](https://docs.sentry.io/product/session-replay)
-* 1GB [attachments](https://docs.sentry.io/platforms/native/guides/minidumps/enriching-events/attachments/)
+## Updating SSO and login methods
-If you need more than this, sign in to your Sentry account and review upgrade options in the **Settings > Usage & Billing > Subscription** section.
+Sentry organizations created via the Fly.io integration use Fly.io SSO by default. After your plan ends (or if you upgrade), you can switch to standard login:
-Note that if you upgrade from this plan, you may not downgrade back to the promotional Team plan.
+1. Go to **Organization Settings > Auth Settings** in Sentry
+1. Click **Disable Fly.io Auth**
+1. Sentry will email each organization member to set a password for their account
-## Instrumenting your application with the Sentry SDK
+This allows you to log in with email and password, and to enable other SSO providers depending on your Sentry subscription level.
-Next, you should instrument your application to start sending exceptions and other events to Sentry.
+For details, see [https://sentry.zendesk.com/hc/en-us/articles/24206530196251](https://sentry.zendesk.com/hc/en-us/articles/24206530196251).
-### Ruby on Rails
+## Do I need to migrate anything after my plan expires?
-Running `bin/rails generate dockerfile --sentry` will:
+No migration steps are needed. Your existing Sentry organization, project, data, alerts, and SDK configuration remain intact.
-* Install the [Ruby Sentry SDK](https://github.com/getsentry/sentry-ruby) rubygem
-* Add an initializer for automatic exception handling and performance tracing
+When your plan changes:
-Sentry can also instrument specific libraries like Sidekiq, Delayed Job, Resque, and more. Check the [Sentry Ruby documentation](https://docs.sentry.io/platforms/ruby/) for more information.
+- Sentry will retain historical data according to the new plan’s retention limits
+- The `SENTRY_DSN` will continue working unless you explicitly remove or rotate it
+- The Sentry SDK will continue to send events, but any events that exceed quota will be dropped
-### Other runtimes and frameworks
+For more details, see [https://sentry.zendesk.com/hc/en-us/articles/36501551064219](https://sentry.zendesk.com/hc/en-us/articles/36501551064219).
-Learn how to set up your application with the Sentry SDK for your runtime in their comprehensive [documentation](https://docs.sentry.io/).
diff --git a/mpg/configuration.html.md b/mpg/configuration.html.md
new file mode 100644
index 0000000000..05ff05923c
--- /dev/null
+++ b/mpg/configuration.html.md
@@ -0,0 +1,135 @@
+---
+title: Cluster configuration options
+layout: docs
+nav: mpg
+date: 2025-08-18
+---
+
+
+
+
+
+
+
+## Connection Pooling
+
+All Managed Postgres clusters come with PGBouncer for connection pooling, which helps manage database connections efficiently. You can configure how PGBouncer assigns connections to clients by changing the pool mode.
+
+### Pool Mode Options
+
+There are two pool modes available:
+
+- **Session**: Connections are assigned to clients for the entire session. This is the default mode and provides the most compatibility with PostgreSQL features, including transactions, prepared statements, and advisory locks.
+- **Transaction**: Connections are assigned per transaction. This mode allows for higher connection reuse and better performance under high load, but has some limitations with certain PostgreSQL features.
+
+### When to Use Each Mode
+
+**Use Session mode when**:
+- Your application uses prepared statements
+- You need advisory locks or other session-specific features
+- You're unsure which mode to choose (Session is the safer default)
+- Your application has long-running transactions
+
+**Use Transaction mode when**:
+- You have a high-throughput application with many short transactions
+- You want to maximize connection reuse
+- Your application primarily uses simple queries without prepared statements
+- You need to support more concurrent connections with the same hardware
+- If you're using Elixir's Ecto library, you must use Transaction pool mode when connecting through the pooler, as Ecto's connection pooling behavior is incompatible with PGBouncer's Session mode.
+
+### Changing Pool Mode from the Dashboard
+
+To change the pool mode for your cluster:
+
+1. Navigate to your MPG cluster's "Connect" tab in the dashboard
+2. Click "View Pooler Settings" to expand the configuration options
+3. Select your desired pool mode.
+4. Click "Update Pool Mode"
+5. Confirm the change in the modal dialog
+
+**Note**: Changing the pool mode will restart the connection pooler nodes, which may cause brief connection interruptions. Your database itself will remain running.
+
+## Changing your MPG Plan
+
+Your Managed Postgres Plan determines the amount of resources your cluster has. All plans include a primary and replica, pg bouncers, and backups. You can change your cluster's plan at any time and the machines in the cluster will be updated to their new resources.
+
+In order to change your plan:
+
+1. Navigate to your MPG cluster's "Settings" tab in the dashboard
+2. Select your desired plan from the list
+3. Click the "Update Configuration" button
+4. Your plan will be updated and all configuration changes will be applied to your database nodes.
+
+
+During the configuration update, your database nodes will restart. You may see a brief period of downtime during the upgrade and switchover. Please make sure to plan accordingly.
+
+
+## Users and Roles
+
+Your Managed Postgres Cluster is created with one admin user named `fly-user`. You can create additional database users and set their roles from your dashboard.
+
+Currently MPG supports the following roles for users:
+
+### Schema Admin
+
+The Schema Admin role is the closest to a Superuser role. It provides full read & write access to your cluster, as well as the ability to modify the structure of your database.
+
+This is the default role granted to the `fly-user` user when your cluster is created.
+
+**What Schema Admin users can do:**
+- Read and write to all databases, tables, and schemas
+- Create, alter, and drop tables, views, functions, and other database objects
+- Create new schemas and manage database structure
+- Connect to any database in the cluster
+- Create temporary tables for session-specific operations
+
+
+**What Schema Admin users cannot do:**
+- Create new databases. This can be done from your Dashboard
+- Other actions requiring superuser permissions, other than those named above.
+
+
+### Writer
+
+The Writer role provides full read and write access to data while restricting the ability to modify the database structure.
+
+**What Writer users can do:**
+- Read from and write to all existing tables and schemas
+- Insert, update, and delete records across all databases
+- Connect to any database in the cluster
+
+**What Writer users cannot do:**
+- Create or modify table structures
+- Create new schemas or databases
+- Create functions, procedures, or other schema objects
+- Create temporary tables
+- Alter database permissions or roles
+
+### Reader
+
+The Reader role provides read-only access to all data in the cluster. This role works well for connecting to reporting or analytics.
+
+**What Reader users can do:**
+- View all data across databases, tables, and schemas
+- Connect to any database in the cluster
+- Run SELECT queries
+
+**What Reader users cannot do:**
+- Insert, update, or delete any data
+- Create any database objects or modify schemas
+- Create temporary tables
+- Modify database structure or permissions
+
+
+### Creating additional users
+
+To create additional users:
+1. Navigate to your MPG cluster's "Users" tab in the dashboard
+2. Enter a name for your new user
+3. Click "Create User" and wait for the user to be created.
+
+Note: If your cluster was created before July 2025, you'll need to opt in to the new Role system before you can add new users. This can be done on the Users tab of your dashboard.
+
+### Authenticating with a custom user
+
+Once the user has been created, you can generate a connection string for them from the "Connect" tab of your dashboard. Select the user you'd like to authenticate as, and the connection string will be updated with their details.
diff --git a/mpg/configuration.md b/mpg/configuration.md
new file mode 100644
index 0000000000..05ff05923c
--- /dev/null
+++ b/mpg/configuration.md
@@ -0,0 +1,135 @@
+---
+title: Cluster configuration options
+layout: docs
+nav: mpg
+date: 2025-08-18
+---
+
+
+
+
+
+
+
+## Connection Pooling
+
+All Managed Postgres clusters come with PGBouncer for connection pooling, which helps manage database connections efficiently. You can configure how PGBouncer assigns connections to clients by changing the pool mode.
+
+### Pool Mode Options
+
+There are two pool modes available:
+
+- **Session**: Connections are assigned to clients for the entire session. This is the default mode and provides the most compatibility with PostgreSQL features, including transactions, prepared statements, and advisory locks.
+- **Transaction**: Connections are assigned per transaction. This mode allows for higher connection reuse and better performance under high load, but has some limitations with certain PostgreSQL features.
+
+### When to Use Each Mode
+
+**Use Session mode when**:
+- Your application uses prepared statements
+- You need advisory locks or other session-specific features
+- You're unsure which mode to choose (Session is the safer default)
+- Your application has long-running transactions
+
+**Use Transaction mode when**:
+- You have a high-throughput application with many short transactions
+- You want to maximize connection reuse
+- Your application primarily uses simple queries without prepared statements
+- You need to support more concurrent connections with the same hardware
+- If you're using Elixir's Ecto library, you must use Transaction pool mode when connecting through the pooler, as Ecto's connection pooling behavior is incompatible with PGBouncer's Session mode.
+
+### Changing Pool Mode from the Dashboard
+
+To change the pool mode for your cluster:
+
+1. Navigate to your MPG cluster's "Connect" tab in the dashboard
+2. Click "View Pooler Settings" to expand the configuration options
+3. Select your desired pool mode.
+4. Click "Update Pool Mode"
+5. Confirm the change in the modal dialog
+
+**Note**: Changing the pool mode will restart the connection pooler nodes, which may cause brief connection interruptions. Your database itself will remain running.
+
+## Changing your MPG Plan
+
+Your Managed Postgres Plan determines the amount of resources your cluster has. All plans include a primary and replica, pg bouncers, and backups. You can change your cluster's plan at any time and the machines in the cluster will be updated to their new resources.
+
+In order to change your plan:
+
+1. Navigate to your MPG cluster's "Settings" tab in the dashboard
+2. Select your desired plan from the list
+3. Click the "Update Configuration" button
+4. Your plan will be updated and all configuration changes will be applied to your database nodes.
+
+
+During the configuration update, your database nodes will restart. You may see a brief period of downtime during the upgrade and switchover. Please make sure to plan accordingly.
+
+
+## Users and Roles
+
+Your Managed Postgres Cluster is created with one admin user named `fly-user`. You can create additional database users and set their roles from your dashboard.
+
+Currently MPG supports the following roles for users:
+
+### Schema Admin
+
+The Schema Admin role is the closest to a Superuser role. It provides full read & write access to your cluster, as well as the ability to modify the structure of your database.
+
+This is the default role granted to the `fly-user` user when your cluster is created.
+
+**What Schema Admin users can do:**
+- Read and write to all databases, tables, and schemas
+- Create, alter, and drop tables, views, functions, and other database objects
+- Create new schemas and manage database structure
+- Connect to any database in the cluster
+- Create temporary tables for session-specific operations
+
+
+**What Schema Admin users cannot do:**
+- Create new databases. This can be done from your Dashboard
+- Other actions requiring superuser permissions, other than those named above.
+
+
+### Writer
+
+The Writer role provides full read and write access to data while restricting the ability to modify the database structure.
+
+**What Writer users can do:**
+- Read from and write to all existing tables and schemas
+- Insert, update, and delete records across all databases
+- Connect to any database in the cluster
+
+**What Writer users cannot do:**
+- Create or modify table structures
+- Create new schemas or databases
+- Create functions, procedures, or other schema objects
+- Create temporary tables
+- Alter database permissions or roles
+
+### Reader
+
+The Reader role provides read-only access to all data in the cluster. This role works well for connecting to reporting or analytics.
+
+**What Reader users can do:**
+- View all data across databases, tables, and schemas
+- Connect to any database in the cluster
+- Run SELECT queries
+
+**What Reader users cannot do:**
+- Insert, update, or delete any data
+- Create any database objects or modify schemas
+- Create temporary tables
+- Modify database structure or permissions
+
+
+### Creating additional users
+
+To create additional users:
+1. Navigate to your MPG cluster's "Users" tab in the dashboard
+2. Enter a name for your new user
+3. Click "Create User" and wait for the user to be created.
+
+Note: If your cluster was created before July 2025, you'll need to opt in to the new Role system before you can add new users. This can be done on the Users tab of your dashboard.
+
+### Authenticating with a custom user
+
+Once the user has been created, you can generate a connection string for them from the "Connect" tab of your dashboard. Select the user you'd like to authenticate as, and the connection string will be updated with their details.
diff --git a/mpg/create-and-connect.html.md b/mpg/create-and-connect.html.md
new file mode 100644
index 0000000000..cccba3bbc8
--- /dev/null
+++ b/mpg/create-and-connect.html.md
@@ -0,0 +1,145 @@
+---
+title: Create and Connect to a Managed Postgres Cluster
+layout: docs
+nav: mpg
+date: 2025-07-11
+---
+
+
+
+
+
+
+
+## Creating a Managed Postgres Cluster from the Dashboard
+
+ To create a new Managed Postgres (MPG) cluster, visit your Fly.io dashboard, select the "Managed Postgres" tab on the left, and click the "Create new cluster" button.
+
+You'll be prompted to set your clusters:
+
+- Cluster name (must be unique within your organization)
+- Region (see [available MPG regions](/docs/mpg/overview/#regions))
+- A plan with predefined hardware resources:
+ - Basic: 2 shared vCPUs, 1GB RAM
+ - Starter: 2 shared vCPUs, 2GB RAM
+ - Launch: 2 Performance vCPUs, 8GB RAM
+ - Scale: 4 Performance vCPUs, 32GB RAM
+ - Performance: 8 Performance vCPUs, 64GB RAM
+- Initial storage size: Up to 500 GB at creation
+- Optional Third Party Extensions to install (Currently only PGVector is supported)
+
+
+
+
+
+After configuring your cluster, select "Create Cluster" and wait a few moments for it to initialize. Once that's complete, you can now connect to your cluster
+
+## Creating a Managed Postgres Cluster from Flyctl
+
+
+
+
+
+You can also create an MPG cluster using the flyctl command line tool. To begin, run:
+
+```cmd
+fly mpg create
+```
+ Follow the prompts to configure your cluster name, plan, and region. By default your cluster will be created with a 10GB volume, but you can specify larger using the `--volume-size` flag:
+
+```cmd
+fly mpg create [flags]
+```
+```out
+ -n, --name string The name of your Postgres cluster
+ -o, --org string The target Fly.io organization
+ --pgvector Enable PGVector for the Postgres cluster
+ --plan string The plan to use for the Postgres cluster (basic, launch, scale)
+ -r, --region string The target region
+ --volume-size int The volume size in GB (default 10)
+```
+
+After all options are set the cluster will begin initializing. Once that's complete, you can now connect to your cluster.
+
+## Attach an application to your Managed Postgres Database
+
+To connect your application running on Fly.io to your Managed Postgres instance, you'll need to add the DB connection details as a secret on your app. This can be done from the Dashboard or via flyctl.
+
+### Attaching an app from your MPG dashboard
+
+From the "Connect" tab of your MPG Cluster page you can attach a specific app in your organization to your database.
+
+1. Select the app to attach in the dropdown
+2. If needed, customize the variable name to use for the database connection URL. The default is `DATABASE_URL`. If your chosen app already has a secret named `DATABASE_URL`, you will need to choose a new variable name before you can deploy.
+3. Select "Set secret and deploy". This will trigger a deploy of your chosen app to add the new secret.
+4. Your app can now use the `DATABASE_URL` environment variable to connect to your database
+
+### Attaching an app using flyctl
+
+To attach an application to your database using flyctl run:
+```cmd
+fly mpg attach -a
+```
+This will also trigger a restart of the selected app in order to add the secret. You can find the cluster ID in your MPG dashboard or by using the `fly mpg list` command.
+
+Both of the attachment options will use the pooled connection URL, which uses PGBouncer to help manage database connections efficiently. This is recommended for most apps.
+
+### Manually connect an application using a connection string
+
+After your database is created, the "Connection" tab will display connection strings for both the pooled connection URL, and the direct database connection address. You can use these to manually connect an app to the database by setting it as a secret in your Fly.io application:
+
+```cmd
+fly secrets set DATABASE_URL="postgres://username:password@host:port/database"
+```
+
+## Connecting from a local machine using flyctl
+
+Because your MPG Cluster runs within your Fly.io private network, it's not accessible over the public internet. You can use flyctl to securely connect to your database from your local machine for development, manual DB work, or to connect to a local tool. All connections using flyctl are securely routed through your organizations [private wireguard network](/docs/networking/private-networking/)
+
+### Connecting with psql
+
+To connect directly to your Managed Postgres database using psql:
+
+```cmd
+fly mpg connect [flags]
+```
+
+This command will create a direct connection to your database and open a psql session. You'll need a compatible psql version installed locally.
+
+### Setting up a Proxy Connection
+
+You can create a proxy connection to your Managed Postgres database using the '[mpg proxy](https://fly.io/docs/flyctl/mpg-proxy/)' command:
+
+```cmd
+fly mpg proxy [flags]
+```
+
+This will open a connection on one of your local ports that forwards to your database.
+
+```out
+$ fly mpg proxy
+? Select Organization: My Organization (personal)
+? Select a Postgres cluster my-test-cluster (iad)
+Proxying localhost:16380 to remote [fdaa:1:2345:0:0::11]:5432
+```
+
+
+This command is useful when you want to connect to your database from your local machine using tools other than psql, such as database management tools or your application in development.
+
+To use the local proxy connection you can modify the direct connection string from your dashboard, replacing the "flympg.net" domain with "localhost":
+
+```out
+Connection String: postgres://fly-user:@localhost:16380/fly-db
+```
+
+## Alternative: Connect via WireGuard
+
+As an alternative to using flyctl proxy commands, you can connect to your MPG cluster directly through your organization's private network using WireGuard.
+
+First, [set up a WireGuard connection to your private network](/docs/blueprints/connect-private-network-wireguard/). Once connected, you can use the direct connection string from your MPG dashboard:
+
+```cmd
+psql "postgresql://fly-user:@direct..flympg.net/fly-db"
+```
+
+This connection method is particularly useful for database management tools that need persistent connections or development environments where you want direct database access.
diff --git a/mpg/extensions.html.md b/mpg/extensions.html.md
new file mode 100644
index 0000000000..f0c03809bb
--- /dev/null
+++ b/mpg/extensions.html.md
@@ -0,0 +1,113 @@
+---
+title: Supported Postgres Extensions
+layout: docs
+nav: mpg
+date: 2025-07-23
+---
+
+
+
+
+
+
+
+## Overview
+
+Fly.io Managed Postgres (MPG) clusters include all modules and extensions provided with the default Postgres 16 distribution. This includes commonly used tools and utilities like `pgcrypto`, `pg_stat_monitor`, and `citext`. The PostgreSQL Extensions page allows you to enable or disable trusted PostgreSQL extensions for your managed database cluster. Extensions add extra functionality to your database, such as new data types, functions, or capabilities.
+
+By default the `plpgsql`, `pg_stat_monitor`, and `pgaudit` extensions are enabled and cannot be disabled.
+
+## Using the Extensions Page
+
+### Search for Extensions
+- Use the search bar to quickly find extensions by name or description
+
+### Select a Database
+- Use the dropdown on the right to choose which database to manage
+- Extensions are enabled/disabled per database, not cluster-wide
+
+### Enable or Disable Extensions
+- Click the toggle switch next to any extension to enable or disable it
+- Each extension displays its version number (e.g., v1.3) showing which version is installed when enabled, or which version will be installed when you enable it
+
+### Additional notes
+
+- If your cluster is still initializing or otherwise degraded, you'll need to wait before managing extensions
+
+## Supported Third Party Extensions
+
+Aside from the extensions bundled with the default Postgres distribution, we are working to support other commonly used third party extensions.
+
+Currently only [Vector](https://github.com/pgvector/pgvector) and [PostGIS](https://postgis.net) are supported, both can be enabled from the Extensions page for your cluster. PostGIS can be enabled when creating a cluster, too:
+
+- Via the dashboard, check "Enable PostGIS" under the "Extensions" section
+- When creating a cluster via flyctl, pass the `--enable-postgis-support` flag:
+
+```cmd
+flyctl mpg create --enable-postgis-support
+```
+
+We plan on supporting additional third party extensions based on user feedback. If there's an extension you rely on or commonly use, please let us know!
+
+## Supported extensions
+
+This is a list of all bundled extensions included in MPG. Not all of these can be enabled at this time, but we are working on adding support for as many extensions as possible.
+
+| Name | Available | Description |
+| --- | --- | --- |
+| `adminpack` | | Support toolpack for pgAdmin to provide additional functionality like remote management of server log files. |
+| `amcheck` | | Provides functions to verify the logical consistency of the structure of indexes, such as B-trees. It's useful for detecting system catalog corruption and index corruption. |
+| `auth_delay` | | Causes the server to pause briefly before reporting authentication failure, to make brute-force attacks on database passwords more difficult. |
+| `auto_explain` | | Automatically logs execution plans of slow SQL statements. It helps in performance analysis by tracking down un-optimized queries in large applications that exceed a specified time threshold. |
+| `basebackup_to_shell` | | Adds a custom basebackup target called shell. This enables an administrator to make a base backup of a running PostgreSQL server to a shell archive. |
+| `basic-archive` | | An archive module that copies completed WAL segment files to the specified directory. Can be used as a starting point for developing own archive module. |
+| `bloom` | | Provides an index access method based on Bloom filters. A Bloom filter is a space-efficient data structure that is used to test whether an element is a member of a set. |
+| `btree_gin` | ✓ | Provides GIN index operator classes with B-tree-like behavior. This allows you to use GIN indexes, which are typically used for full-text search, in situations where you might otherwise use a B-tree index, such as with integer or text data. |
+| `btree_gist` | ✓ | Provides GiST (Generalized Search Tree) index operator classes that implement B-tree-like behavior. This allows you to use GiST indexes, which are typically used for multidimensional and non-scalar data, in situations where you might otherwise use a B-tree index, such as with integer or text data. |
+| `citext` | ✓ | Provides a case-insensitive character string type, citext. Essentially, it internally calls lower when comparing values. Otherwise, it behaves almost exactly like text. |
+| `cube` | ✓ | Implements a data type cube for representing multidimensional cubes |
+| `dblink` | | Provides functions to connect to other PostgreSQL databases from within a database session. This allows for queries to be run across multiple databases as if they were on the same server. |
+| `dict_int` | ✓ | An example of an add-on dictionary template for full-text search. It's used to demonstrate how to create custom dictionaries in PostgreSQL. |
+| `dict_xsyn` | | Example synonym full-text search dictionary. This dictionary type replaces words with groups of their synonyms, and so makes it possible to search for a word using any of its synonyms. |
+| `earthdistance` | | This module provides two different approaches to calculating great circle distances on the surface of the Earth. The first one depends on the cube module. The second one is based on the built-in point data type, using longitude and latitude for the coordinates. |
+| `fuzzystrmatch` | ✓ | Determine similarities and distance between strings |
+| `hstore` | ✓ | Implements the hstore data type for storing sets of key/value pairs within a single PostgreSQL value. |
+| `intagg` | | Integer aggregator and enumerator. |
+| `intarray` | ✓ | Provides a number of useful functions and operators for manipulating null-free arrays of integers. |
+| `isn` | ✓ | Provides data types for the following international product numbering standards: EAN13, UPC, ISBN (books), ISMN (music), and ISSN (serials). |
+| `jsonb_plperl` | | Transform between jsonb and plperl |
+| `lo` | ✓ | Provides support for managing Large Objects (also called LOs or BLOBs). This includes a data type lo and a trigger lo_manage. |
+| `ltree` | ✓ | Implements a data type ltree for representing labels of data stored in a hierarchical tree-like structure. Extensive facilities for searching through label trees are provided. |
+| `oldsnapshot` | | Allows inspection of the server state that is used to implement old_snapshot_threshold. |
+| `pageinspect` | | Provides functions that allow you to inspect the contents of database pages at a low level, which is useful for debugging purposes. |
+| `passwordcheck` | | Checks users' passwords whenever they are set with CREATE ROLE or ALTER ROLE. If a password is considered too weak, it will be rejected and the command will terminate with an error. |
+| `pg_buffercache` | | Provides the set of functions for examining what's happening in the shared buffer cache in real time. |
+| `pg_freespacemap` | | Provides a means of examining the free space map (FSM), which PostgreSQL uses to track the locations of available space in tables and indexes. This can be useful for understanding space utilization and planning for maintenance operations. |
+| `pg_prewarm` | | Provides a convenient way to load relation data into either the operating system buffer cache or the PostgreSQL buffer cache. This can be useful for reducing the time needed for a newly started database to reach its full performance potential by preloading frequently accessed data. |
+| `pg_stat_monitor` | ✓ | A more advanced version of pg_stat_statements for tracking planning and execution statistics of all SQL statements executed by a server. Includes all columns available in pg_stat_statements plus provides additional ones.|
+| `pg_stat_statements` | | A module for tracking planning and execution statistics of all SQL statements executed by a server. Consider using an advanced version of pg_stat_statements - pg_stat_monitor |
+| `pg_surgery` | | Provides various functions to perform surgery on a damaged relation. These functions are unsafe by design and using them may corrupt (or further corrupt) your database. Use them with caution and only as a last resort |
+| `pg_trgm` | ✓ | Provides functions and operators for determining the similarity of alphanumeric text based on trigram matching. A trigram is a contiguous sequence of three characters. The extension can be used for text search and pattern matching operations. |
+| `pg_visibility` | | Provides a way to examine the visibility map (VM) and the page-level visibility information of a table. It also provides functions to check the integrity of a visibility map and to force it to be rebuilt. |
+| `pg_walinspect` | | Provides SQL functions that allow you to inspect the contents of write-ahead log of a running PostgreSQL database cluster at a low level, which is useful for debugging, analytical, reporting or educational purposes. |
+| `pgcrypto` | ✓ | Provides cryptographic functions for PostgreSQL. |
+| `pgrowlocks` | | Provides a function to show row locking information for a specified table. |
+| `pgstattuple` | | Provides various functions to obtain tuple-level statistics. It offers detailed information about tables and indexes, such as the amount of free space and the number of live and dead tuples. |
+| `plpgsql` | ✓ | PL/pgSQL procedural language |
+| `PostGIS` | ✓ | Geographic information system extension for PostgreSQL |
+| `postgis_raster` | ✓ | Raster data support for PostGIS |
+| `postgis_sfcgal` | ✓ | SFCGAL support for PostGIS |
+| `postgis_topology` | ✓ | Topology data support for PostGIS |
+| `postgres_fdw` | | Provides a Foreign Data Wrapper (FDW) for accessing data in remote PostgreSQL servers. It allows a PostgreSQL database to interact with remote tables as if they were local. |
+| `seg` | ✓ | Implements a data type seg for representing line segments, or floating point intervals. seg can represent uncertainty in the interval endpoints, making it especially useful for representing laboratory measurements. |
+| `segpgsql` | | SELinux-, label-based mandatory access control (MAC) security module. It can only be used on Linux 2.6.28 or higher with SELinux enabled. |
+| `spi` | | Provides several workable examples of using the Server Programming Interface (SPI) and triggers. |
+| `sslinfo` | | Provides information about the SSL certificate that the current client provided when connecting to PostgreSQL. |
+| `tablefunc` | ✓ | Includes various functions that return tables (that is, multiple rows). These functions are useful both in their own right and as examples of how to write C functions that return multiple rows. |
+| `tcn` | ✓ | Provides a trigger function that notifies listeners of changes to any table on which it is attached. |
+| `test_decoding` | | An SQL-based test/example module for WAL logical decoding |
+| `tsm_system_rows` | ✓ | Provides the table sampling method SYSTEM_ROWS, which can be used in the TABLESAMPLE clause of a SELECT command. |
+| `tsm_system_time` | ✓ | Provides the table sampling method SYSTEM_TIME, which can be used in the TABLESAMPLE clause of a SELECT command. |
+| `unaccent` | ✓ | A text search dictionary that removes accents (diacritic signs) from lexemes. It's a filtering dictionary, which means its output is always passed to the next dictionary (if any). This allows accent-insensitive processing for full text search. |
+| `uuid-ossp` | ✓ | Provides functions to generate universally unique identifiers (UUIDs) using one of several standard algorithms |
+| `vector` | ✓ | vector data type and ivfflat and hnsw access methods |
diff --git a/mpg/guides-examples/phoenix-guide.html.md b/mpg/guides-examples/phoenix-guide.html.md
new file mode 100644
index 0000000000..811780fc1f
--- /dev/null
+++ b/mpg/guides-examples/phoenix-guide.html.md
@@ -0,0 +1,121 @@
+---
+title: "Phoenix with Managed Postgres"
+layout: docs
+nav: mpg
+date: 2025-09-16
+author: Kaelyn
+---
+
+This guide explains the key **Managed Postgres (MPG)-specific adjustments** you need when connecting a Phoenix app. We'll focus on:
+
+1. Connection Pooling Settings
+2. Running Migrations
+3. Using Oban with MPG
+4. Troubleshooting and Common Issues
+
+This guide doesn’t cover Phoenix or Ecto setup—we assume you already have that in place.
+
+## Connection Pooling with PgBouncer + Ecto
+
+Fly.io MPG uses [PgBouncer](https://www.pgbouncer.org/) for connection pooling. By default, MPG clusters run in **Session** mode but Ecto requires **Transaction** mode due to how it handles connection pooling.
+
+To configure PgBouncer:
+
+1. Open your MPG cluster in the dashboard.
+2. Go to **Connect → Pooler settings**.
+3. Set **Pool mode** to **Transaction**.
+
+You'll also need to update your repo config:
+
+```elixir
+config :my_app, MyApp.Repo,
+ url: System.fetch_env!("DATABASE_URL"),
+ pool_size: 8,
+ timeout: 15_000,
+ prepare: :unnamed
+```
+
+`prepare: :unnamed` is required because named prepared statements don't work with PgBouncer in transaction mode.
+
+## Running Migrations
+
+While you'll need to use Transaction mode with Ecto for most cases, Migrations are a special case. Migrations rely on [advisory locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS) and session stickiness, which PgBouncer's [transaction pooling mode](https://www.pgbouncer.org/usage.html#pool-modes) doesn't support. For running migrations you'll need to configure your app to use the **direct database URL** instead.
+
+Update your secrets to add a `DIRECT_DATABASE_URL`
+
+```bash
+fly secrets set \
+ DATABASE_URL="postgresql://...@pgbouncer..flympg.net/fly-db" \
+ DIRECT_DATABASE_URL="postgresql://...@direct..flympg.net/fly-db"
+```
+
+In your fly.toml, update your release command to use the direct connection for running your migration:
+
+```toml
+[deploy]
+ release_command = "/bin/sh -lc 'DATABASE_URL=$DIRECT_DATABASE_URL bin/migrate'"
+```
+
+Or run a migration manually:
+
+```bash
+fly ssh console -C '/bin/sh -lc "DATABASE_URL=\"$DIRECT_DATABASE_URL\" bin/migrate"'
+```
+
+## Using Oban with MPG
+
+If you're using the [Oban library](https://hexdocs.pm/oban/Oban.html) for handling Job queues, you'll need to make a few adjustments as PgBouncer in transaction mode doesn't support `LISTEN/NOTIFY`, which Oban uses for job notifications. You have a few options:
+
+### 1. Use a non-Postgres notifier
+
+```elixir
+# Distributed Erlang notifier
+config :my_app, Oban,
+ repo: MyApp.ObanRepo,
+ notifier: Oban.Notifiers.PG,
+ queues: [default: 10]
+```
+
+Or with Phoenix PubSub:
+
+```elixir
+config :my_app, Oban,
+ repo: MyApp.ObanRepo,
+ notifier: {Oban.Notifiers.Phoenix, pubsub: MyApp.PubSub},
+ queues: [default: 10]
+```
+
+### 2. Run Oban on a direct connection
+
+If you have a requirement to use Postgres `LISTEN/NOTIFY`, you can set up Oban to use a direct database connection instead of using PG Bouncer
+
+```elixir
+config :my_app, Oban,
+ repo: MyApp.DirectRepo,
+ notifier: Oban.Notifiers.Postgres,
+ queues: [default: 10]
+```
+
+This uses direct DB connections. Each connection is an Ecto pool entry that bypasses PgBouncer. Because direct connections are limited, keep the pool very small.
+
+### 3. Legacy fallback (Oban < 2.14)
+
+Older versions required the Repeater plugin. Since Oban 2.14 (2023), polling fallback is built-in and Repeater is deprecated.
+
+## Troubleshooting and common errors
+
+### Troubleshooting checklist
+
+- Set PgBouncer pool mode to Transaction.
+- Set `prepare: :unnamed` on every Repo.
+- Use the pooled URL for app traffic and the direct URL for migrations and other session-scoped operations (advisory locks, etc.).
+- Keep Ecto pools modest on the Basic plan (API repo 8, Oban repo 6). Reduce if most connections sit idle.
+- Check MPG metrics around error timestamps for restarts or memory pressure.
+
+### Common errors and fixes
+
+- `tcp recv (idle): closed` or `tcp recv (idle): timeout` — These are idle connection reclaimed by the pooler, and don't represent an issue as Ecto reconnects automatically. To remove them, lower your pool size or ignore.
+- `FATAL 08P01 protocol_violation` on login — Set `prepare: :unnamed` and ensure PgBouncer is in Transaction mode.
+- Oban jobs not running — Use a non-Postgres notifier (PG or Phoenix) behind PgBouncer, or run Oban on a direct Repo. On Oban ≥ 2.14, do not add Repeater (polling fallback is automatic when PubSub isn't available).
+- Migrations hanging or failing — Run migrations with the direct database URL (via `release_command` or a one-off SSH command), not through PgBouncer.
+- `non-existing domain - :nxdomain` - Make sure [ipv6 is enabled](https://fly.io/docs/elixir/getting-started/#important-ipv6-settings)
diff --git a/mpg/import.html.md b/mpg/import.html.md
new file mode 100644
index 0000000000..a1f1e83e13
--- /dev/null
+++ b/mpg/import.html.md
@@ -0,0 +1,64 @@
+---
+title: Import data from another postgres cluster
+layout: docs
+nav: mpg
+date: 2025-07-11
+---
+
+
+
+
+
+
+
+## Import data from another postgres cluster into your Managed Postgres Cluster
+
+If you're migrating to Fly Managed Postgres (MPG) from an unmanaged Fly Postgres cluster or another Postgres provider, you'll need to import your data into your MPG cluster.
+
+There isn't currently an automated way to migrate an existing DB's data, but you can accomplish this with a standard Postgres dump and restore process after creating your MPG cluster.
+
+For simple databases, this can be as easy as running :
+
+```cmd
+ pg_dump "$LEGACY_PG_CONNECTION_STRING" | psql "$MPG_URI".
+```
+
+Because your MPG cluster runs within your Fly Private Network, you'll need to be connected to your organizations Wireguard network in order to restore into your MPG database. To run the import process from your local machine, you would take the following steps:
+
+1. Create your Managed Postgres cluster from the Fly.io dashboard, or using `fly mpg create`
+
+
+ Warning: Your new cluster must be created with a volume at least as large as the database you're importing, otherwise the import process will fail.
+
+
+2. Open a proxy connection to your MPG database with
+ ```out
+ $ fly mpg proxy
+ ? Select Organization: My Organization (personal)
+ ? Select a Postgres cluster my-test-cluster (iad)
+ Proxying localhost:16380 to remote [fdaa:1:2345:0:0::67]:5432
+ ```
+
+3. Find your MPG cluster's connection string in the dashboard, and modify it to use the local proxy:
+ ```out
+ Connection String: postgres://fly-user:@localhost:16380/fly-db
+ ```
+
+4. Run the dump and restore command
+```cmd
+ pg_dump "$LEGACY_PG_CONNECTION_STRING" | psql postgres://fly-user:@localhost:16380/fly-db
+```
+
+5. If everything succeeds, all data from your legacy cluster will be imported into your new MPG cluster.
+
+For larger or more complex databases, you may need to break the dump and restore process into multiple steps. You may also consider using dedicated PG migration tools like [pgloader](https://pgloader.io/) or [pgcopydb](https://github.com/dimitri/pgcopydb).
+
+## Limitations
+Some source databases may not be a good fit for importing into a Fly Managed Postgres cluster. Some common cases that will prevent imports are if your source database:
+
+- Relies on a third party extension that [isn't supported by MPG](/docs/mpg/extensions)
+- Is larger than 1 TB (the maximum storage limit for MPG clusters)
+
+Note: While MPG supports up to 1 TB of storage, initial cluster creation is limited to 500 GB. For databases larger than 500 GB, you'll need to create your cluster with the maximum initial size and then expand storage after the import process.
+
+Multi-schema and multi-database imports are supported. If your app uses multiple schemas or databases, imports should work as expected when run with the default `fly-user` or another user with the `schema_admin` role.
diff --git a/mpg/index.html.md b/mpg/index.html.md
new file mode 100644
index 0000000000..da7fa3b323
--- /dev/null
+++ b/mpg/index.html.md
@@ -0,0 +1,93 @@
+---
+title: Managed Postgres
+layout: docs
+nav: mpg
+date: 2025-07-10
+redirect_from: /docs/mpg/overview/
+---
+
+
+
+
+
+## What is Managed Postgres?
+
+Fly.io's Managed Postgres is our fully-managed database service that handles all aspects of running production PostgreSQL databases where we take care of:
+
+- Automatic backups and recovery
+- High availability with automatic failover
+- Performance monitoring and metrics
+- Resource scaling (CPU, RAM, storage)
+- 24/7 support and incident response
+- Automatic encryption of data at rest and in transit
+
+### What's included
+
+You'll be able to access:
+
+- A highly-available Postgres cluster within your Fly.io organization's [private network](/docs/networking/private-networking/)
+- Multiple databases and schemas on that cluster
+- Fly.io Support Portal to log tickets and get help
+- Any trusted extensions included in the [default Postgres 16 distribution](https://www.postgresql.org/docs/16/contrib.html)
+- The third party `pgvector` extension for vector similarity search
+- The third party `PostGIS` extension for adding geospatial data support, if enabled when provisioning your database
+
+### What's not there yet
+
+At the moment, the following features are under development:
+
+- Security patches and version upgrades
+- Third Party Postgres extensions besides `pgvector` or `postGIS`
+- Customer-facing alerting
+- Database migration tools
+
+We're working on expanding these capabilities and will provide updates as they become available.
+
+## Regions
+
+The current regions available for deploying Fly.io Managed Postgres are:
+
+- `ams` - Amsterdam, Netherlands
+- `fra` - Frankfurt, Germany
+- `gru` - São Paulo, Brazil
+- `iad` - Ashburn, Virginia, USA
+- `lax` - Los Angeles, California, USA
+- `nrt` - Tokyo, Japan
+- `ord` - Chicago, Illinois, USA
+- `sin` - Singapore
+- `sjc` - San Jose, California, USA
+- `syd` - Sydney, Australia
+
+We'll be rolling out more regions as soon as we can. Choose a region close to your application for optimal performance.
+
+## Database Storage
+
+Managed Postgres storage features:
+
+- Maximum storage limit: 1 TB
+- Initial storage size: Up to 500 GB at creation
+- Storage is replicated across all nodes in your cluster
+- Storage growth is monitored and managed automatically
+
+## Pricing
+
+The price of running Fly.io Managed Postgres depends on:
+
+- Your selected Managed Postgres Plan
+- The amount of storage your cluster has
+
+Your MPG plan determines the CPU and Memory configuration for your cluster. All plans include high availability, backups, and connection pooling.
+
+The current monthly plan pricing is:
+
+| Plan | CPU | Memory | Monthly Price |
+| --- | --- | --- | --- |
+| Basic | Shared-2x | 1GB | $38.00 |
+| Starter | Shared-2x | 2GB | $72.00|
+| Launch | Performance-2x| 8GB | $282.00 |
+| Scale | Performance-4x | 32GB | $962.00 |
+| Performance | Performance-8x | 64GB | $1,922.00 |
+
+Database storage is priced at **$0.28 per provisioned GB for a 30-day month**. For example, if you have 10GB of storage provisioned for your cluster, your monthly storage cost will be $2.80.
+
+Clusters created or deleted mid-month will have their pricing prorated accordingly.
diff --git a/mpg/metrics.html.md b/mpg/metrics.html.md
new file mode 100644
index 0000000000..7ecbad464f
--- /dev/null
+++ b/mpg/metrics.html.md
@@ -0,0 +1,63 @@
+---
+title: Monitoring and Metrics
+layout: docs
+nav: mpg
+---
+
+
+
+
+
+## Performance Monitoring
+
+The Managed Postgres dashboard provides comprehensive performance monitoring and metrics for your PostgreSQL clusters. The **Metrics** tab gives you real-time visibility into your database's performance, helping you identify bottlenecks and optimize your applications.
+
+### Accessing Metrics
+
+To view metrics for your cluster:
+
+1. Navigate to your MPG cluster in the Fly.io dashboard
+2. Click the **Metrics** tab
+3. Optionally filter by specific database using the database dropdown
+4. Select your desired time range (15 minutes, 1 hour, 6 hours, 24 hours, or 2 days)
+
+### Available Charts and Metrics
+
+The metrics dashboard provides detailed insights through various charts and gauges organized into categories:
+
+#### System Resource Metrics
+
+| Metric | Description |
+|--------|-------------|
+| **Database CPU Utilization** | CPU usage percentage for each database instance, labeled with Primary (P) and Replica (R) badges. Shows individual instance IDs and averages over the selected time period |
+| **Database Memory Utilization** | Memory usage for Primary and Replica database instances. Displays current usage and average consumption per instance |
+| **Pooler CPU Utilization** | CPU usage percentage for PGBouncer connection pooler instances. Typically remains low under normal operations |
+| **Pooler Memory Utilization** | Memory consumption in MB for connection pooler instances. Shows usage for each pooler with instance IDs |
+
+#### Connection Metrics
+
+| Metric | Description |
+|--------|-------------|
+| **Database Connections** | Shows active and idle database connections over time |
+| **Pooler Connections** | Tracks active and waiting connections through PGBouncer
+ |
+#### Database Operations
+
+| Metric | Description |
+|--------|-------------|
+| **Database Operations** | Row-level operations per second broken down by type (inserts, updates, deletes, and fetches). Shows throughput patterns and workload distribution across operation types |
+| **Cache Hit Ratio** | Percentage of data requests served from memory versus disk. Higher percentages (95%+) indicate efficient memory usage. Lower values suggest queries are hitting disk frequently |
+| **Deadlocks** | Count of database deadlocks detected when two or more transactions are waiting for each other to release locks. Shows frequency and timing of these mutual blocking situations |
+
+#### Storage
+
+| Metric | Description |
+|--------|-------------|
+| **Database Size** | Current storage usage across databases |
+
+#### Replication Metrics
+
+| Metric | Description |
+|--------|-------------|
+| **Replication Delay Bytes** | Amount of WAL (Write-Ahead Log) data in bytes that the replica is behind the primary. Measures the volume of changes waiting to be applied |
+| **Replication Delay Seconds** | Time in seconds that the replica lags behind the primary database. Represents how long ago the replica's current state reflects the primary |
diff --git a/mpg/overview.html.md b/mpg/overview.html.md
new file mode 100644
index 0000000000..c35f5d0583
--- /dev/null
+++ b/mpg/overview.html.md
@@ -0,0 +1,135 @@
+---
+title: Managed Postgres Overview
+layout: docs
+nav: mpg
+toc: false
+---
+
+
+
+
+
+
+## What is Managed Postgres?
+
+Fly.io's Managed Postgres is our database-as-a-service offering where we handle:
+
+- Automatic backups and recovery
+- High availability with automatic failover
+- Performance monitoring and metrics
+- Resource scaling (CPU, RAM, storage)
+- 24/7 support and incident response
+- Automatic encryption of data at rest and in transit
+
+### What's included
+
+You'll be able to access:
+
+- A highly-available Postgres cluster within your Fly.io organization's [private network](/docs/networking/private-networking/)
+- A single database on that cluster
+- Fly.io Support Portal to log tickets and get help
+- Any modules and extensions included in the [default Postgres 16 distribution](https://www.postgresql.org/docs/16/contrib.html)
+- The third party `pgvector` extension for vector similarity search, if enabled when provisioning your database
+
+### What's not there yet
+
+At the moment, the following features are under development:
+
+- Security patches and version upgrades
+- Multiple databases or schemas per cluster
+- Third Party Postgres extensions besides `pgvector`
+- Customer-facing monitoring and alerting
+- Database migration tools
+
+We're working on expanding these capabilities and will provide updates as they become available.
+
+## Creating a Managed Postgres Instance
+
+To create a new Managed Postgres cluster, visit your Fly.io dashboard and click the "Create new cluster" button in the Managed Postgres section.
+
+You'll be prompted to choose:
+
+- Cluster name (must be unique within your organization)
+- Region (see available regions below)
+- A plan with predefined hardware resources:
+ - Basic: 2 shared vCPUs, 1GB RAM
+ - Starter: 2 shared vCPUs, 2GB RAM
+ - Launch: 2 Performance vCPUs, 8GB RAM
+ - Scale: 4 Performance vCPUs, 32GB RAM
+ - Performance: 8 Performance vCPUs, 64GB RAM
+- Storage size (up to 500GB at creation)
+
+
+
+
+
+## Connecting to Your Managed Postgres Database
+
+To connect your Fly.io application to your Managed Postgres instance:
+
+1. After creation, the "Connection" tab will display your connection string
+2. Set it as a secret in your Fly.io application:
+
+```cmd
+fly secrets set DATABASE_URL="postgres://username:password@host:port/database"
+```
+
+3. Your application can now use the `DATABASE_URL` environment variable to connect
+
+For security, the connection string uses SSL by default. Make sure your application's Postgres client is configured to use SSL.
+
+## Using flyctl with Managed Postgres
+
+You can interact with your Managed Postgres instances using the Fly.io CLI (`flyctl`). Here are the key commands:
+
+### Connecting with psql
+
+To connect directly to your Managed Postgres database using psql:
+
+```cmd
+fly mpg connect [flags]
+```
+
+This command will establish a direct connection to your database using the psql client. You'll need psql installed locally.
+
+### Setting up a Proxy Connection
+
+To create a proxy connection to your Managed Postgres database:
+
+```cmd
+fly mpg proxy [flags]
+```
+
+This command is useful when you want to connect to your database from your local machine using tools other than psql, such as database management tools or your application in development.
+
+## Regions
+
+The current regions available for deploying Fly.io Managed Postgres are:
+
+- `fra` - Frankfurt, Germany
+- `gru` - São Paulo, Brazil
+- `iad` - Ashburn, USA
+- `lax` - Los Angeles, USA
+- `ord` - Chicago, USA
+- `syd` - Sydney, Australia
+
+We'll be rolling out more regions as soon as we can. Choose a region close to your application for optimal performance.
+
+## Database Storage
+
+Managed Postgres storage features:
+
+- Maximum storage limit: 1 TB
+- Initial storage size: Up to 500 GB at creation
+- Storage is replicated across all nodes in your cluster
+- Storage growth is monitored and managed automatically
+
+## Pricing
+
+The price of running Fly.io Managed Postgres depends on:
+
+- CPU/Memory configuration (Launch, Performance, or Enterprise plans)
+- Region in which you're deploying
+- Storage usage
+
+Database storage is priced at **$0.28 per GB for a 30-day month**. You can view detailed pricing in your Fly.io dashboard.
diff --git a/networking/custom-domain-api.html.markerb b/networking/custom-domain-api.html.markerb
index 0889376932..d334f11a57 100644
--- a/networking/custom-domain-api.html.markerb
+++ b/networking/custom-domain-api.html.markerb
@@ -76,6 +76,7 @@ mutation($appId: ID!, $hostname: String!) {
configured
acmeDnsConfigured
acmeAlpnConfigured
+ isAcmeHttpConfigured
certificateAuthority
certificateRequestedAt
dnsProvider
@@ -100,6 +101,7 @@ mutation($appId: ID!, $hostname: String!) {
"configured": true,
"acmeDnsConfigured": true,
"acmeAlpnConfigured": true,
+ "isAcmeHttpConfigured": true,
"certificateAuthority": "lets_encrypt",
"certificateRequestedAt": "2020-03-06T12:26:36Z",
"dnsProvider": "enom",
@@ -129,6 +131,7 @@ query($appName: String!, $hostname: String!) {
configured
acmeDnsConfigured
acmeAlpnConfigured
+ isAcmeHttpConfigured
certificateAuthority
createdAt
dnsProvider
@@ -159,6 +162,7 @@ query($appName: String!, $hostname: String!) {
"configured": true,
"acmeDnsConfigured": true,
"acmeAlpnConfigured": true,
+ "isAcmeHttpConfigured": true,
"certificateAuthority": "lets_encrypt",
"createdAt": "2020-03-04T17:17:39Z",
"dnsProvider": "enom",
@@ -202,6 +206,7 @@ query($appName: String!, $hostname: String!) {
configured
acmeDnsConfigured
acmeAlpnConfigured
+ isAcmeHttpConfigured
certificateAuthority
createdAt
dnsProvider
diff --git a/networking/custom-domain.html.markerb b/networking/custom-domain.html.markerb
index 614b3fa78e..5059e92154 100644
--- a/networking/custom-domain.html.markerb
+++ b/networking/custom-domain.html.markerb
@@ -12,129 +12,92 @@ redirect_from:
-When you create a Fly App, it is automatically given a `fly.dev` subdomain, based on the app's name. This is great for testing, but when you want to go to full production you'll want your application to appear on your own domain and have HTTPS set up for you as it is with your `.fly.dev` domain. You can do this by setting DNS records through your DNS provider and adding a TLS certificate on Fly.io for your custom domain.
+When you create a Fly App, it is automatically given a `fly.dev` subdomain, based on the app's name. This is great for testing, but when you want to go to full production you'll want your application to appear on your own domain and have HTTPS set up for you as it is with your `.fly.dev` domain. You can do this by adding your domain to your Fly App, then configuring DNS records through your DNS provider.
## Add a custom domain for your app
-The order of the tasks to add a custom domain depends on when you want start directing traffic to your app:
+To add a custom domain, first attach your domain to your Fly App to get DNS configuration instructions, then configure your DNS records with your DNS provider.
-* When you want to start accepting traffic immediately on your custom domain, follow the sections below in order.
+### Add your domain to your app
-* When you want to get certificates before your app starts accepting traffic, for example if you're cutting over from another platform to Fly.io, you should create the certificates and configure the ACME challenge first, and then add the DNS records when you're ready to send traffic to your app.
+Attach your domain to your Fly App. This prepares your app to generate TLS certificates and handle traffic for your custom domain, and shows you the DNS configuration options for your setup.
-### Add DNS records
+You can add your domain with flyctl or in your app [dashboard](https://fly.io/dashboard/) under **Certificates**.
-Direct traffic to your site by mapping your custom domain name to your Fly App through your DNS provider. Create either a CNAME (Option I) or A/AAAA (Option II) record.
+For example, using the CLI:
-#### Option I: Set a CNAME record
-
-In most cases, you can use a CNAME record, which points your custom domain at your `.fly.dev` host. If your DNS provider doesn't allow Apex, or root, hostnames to have CNAME records, then you can use *Option II: Set the A/AAAA records*.
+```cmd
+fly certs add example.com
+```
-Set the CNAME record with your DNS provider. For example, if you have a domain called `example.com` and an app called `exemplum`, then you can create a CNAME record for `example.com`'s DNS that would look like this:
+This command will show you the applicable DNS configuration options for your setup, including:
+- A and AAAA records
+- CNAME records
+- Proxy setup (if using a CDN)
+- DNS-01 challenge (required for wildcards, optional for pre-traffic certificate generation)
-| Record Type | Host / Hostname / Name | Value / Content / Alias of |
-| --- | --- | ---|
-| CNAME | @ | exemplum.fly.dev |
+If you are adding a wildcard domain with the CLI, put quotes around the hostname to avoid shell expansion:
-Different DNS providers might use different terms for the parts of the record and we've listed a few of them in the table above.
+```cmd
+fly certs add "*.example.com"
+```
-For our example, with this CNAME record added, accessing `example.com` will tell the DNS system to look up `exemplum.fly.dev` and return its results.
+Use `fly certs check ` to check the certificate status and validation progress, or `fly certs setup ` to view the setup instructions again.
-#### Option II: Set the A/AAAA records
+### Configure DNS records
-A and AAAA records use the app's IP addresses rather than the domain name. This option might be slightly faster than the CNAME record lookup.
+Now that you've added your domain to your app, configure DNS records with your DNS provider to direct traffic to your Fly App. The setup instructions in the dashboard, or from `fly certs add`, show the recommended DNS configuration for your situation.
-Get your app's IP addresses:
+Choose the DNS setup that matches your needs:
-```cmd
-fly ips list
-```
-```output
-VERSION IP TYPE REGION CREATED AT
-v6 2a09:8280:1::39:b14f:0 public (dedicated) global Jun 18 2024 17:09
-v4 66.241.124.193 public (shared) Jan 1 0001 00:00
-```
+#### A and AAAA records (recommended)
-Set the A and AAAA records with your DNS provider. Add an A record for your domain that points to the IPv4 address, and add an AAAA record for your domain that points to the IPv6 address.
+Use A and AAAA records for most direct connections to your app. These records point your domain directly to your app's IP addresses.
-Once the A and AAAA records are added and propagated through the DNS system, you should be able to connect over unencrypted HTTP to the domain name. Continuing the preceding example, that's the domain name: `http://example.com`.
+The A and AAAA records you need to set will be shown in your dashboard, and in the output from `fly certs add`. If your app doesn't have an IPv6 address, allocate one with `fly ips allocate-v6`.
-**Important:** Hostname validation will fail without an IPv6 address—and we won't attempt to issue or renew a certificate—unless you're using a [CNAME `_acme-challenge` for domain verification](#optional-validate-with-an-acme-challenge). However, we still recommend having both an IPv4 and an IPv6 address allocated if your app is serving traffic. If your app doesn't have an IPv6 address, allocate one with `flyctl ips allocate-v6`.
+**Important:** Hostname validation will fail without an IPv6 address—and we won't attempt to issue or renew a certificate—unless you're using a [CNAME `_acme-challenge` for domain verification](#dns-challenge). However, we still recommend having both an IPv4 and an IPv6 address allocated if your app is serving traffic.
-### Get certified
+#### CNAME records
-You'll need a TLS certificate for your domain if your app:
+CNAME records work well for subdomains (like `www.example.com` or `app.example.com`). A CNAME points your custom domain at a unique `.fly.dev` hostname for your app.
+CNAME records are also a good option if you have many IP addresses assigned to your app, or expect to change them in the future.
-* Should accept HTTPS connections, or
-* Uses a shared IPv4 [Anycast address](/docs/networking/services/#ip-addresses). Fly Proxy uses the certificate to associate the custom domain name with your app for routing purposes.
+Set the CNAME record with your DNS provider. Each app has a unique CNAME target that will be shown in the dashboard for your certificate, or in the output from `fly certs add`.
-You can add certificates with flyctl or in your app [dashboard](https://fly.io/dashboard/) under **Certificates**.
+Setting CNAME records for the apex domain can be problematic, and should be avoided unless your DNS provider supports CNAME flattening. Some providers use a special name for these records, such as ANAME or ALIAS, and some will flatten a CNAME at the apex automatically. In general, we recommend setting A/AAAA records on the apex domain.
-Create a certificate for your custom domain with the `fly certs add` command. For example:
+#### Proxy/CDN setup
-```cmd
-fly certs add example.com
-```
-```output
- Hostname = example.com
- Configured = true
- Issued =
- Certificate Authority = lets_encrypt
- DNS Provider = enom
- DNS Validation Instructions =
- DNS Validation Hostname =
- DNS Validation Target = example.com.5xzw.flydns.net
- Source = fly
- Created At = 0001-01-01T00:00:00Z
- Status =
-```
+If you're using a proxy or CDN (like Cloudflare) in front of your Fly application, configure only an AAAA record pointing to your app's IPv6 address, as certificate generation requires IPv6 traffic.
-If you need a wildcard domain, put quotes around the hostname to avoid shell expansion:
+#### DNS Challenge
-```cmd
-fly certs add "*.example.com"
-```
+Use the DNS-01 challenge when:
-Running `fly certs add` starts the process of getting a certificate. `Configured` should be true and `Status` will change to `Ready` when the certificates are available. You can run `fly certs show ` to check the status.
+- You need a wildcard certificate
+- You want to generate the certificate before directing traffic to your app
+- Automatic validation methods don't work for your setup
+This will require setting an `_acme-challenge` CNAME record on your domain. The required record will be shown in your dashboard, or in the output from `fly certs add`.
-### (Optional) Validate with an ACME DNS-01 challenge
+### Certificate validation
-Validate your domain with an ACME DNS-01 challenge if one or more of the following scenarios apply:
+After you have configured your DNS, Fly.io automatically validates your domain ownership and issues certificates. This happens through one of these methods:
-- You want to issue a certificate before creating the CNAME or A/AAAA records to point to your app.
-- You're using a proxy like Cloudflare, [which prevents our systems from verifying the source IP addresses](#i-use-cloudflare-and-there-seems-to-be-a-problem-issuing-or-validating-my-flyio-tls-certificate).
-- You want to ensure no HTTPS connection errors occur during the short time (usually minutes) it takes to generate the first-ever certificate for your site.
-- You're using a wildcard certificate.
+- **TLS-ALPN challenge**: Validates through a TLS handshake with Fly Proxy. This is the preferred method and works automatically for direct connections.
-1. Run the `fly certs show ` command. For example:
+- **HTTP-01 challenge**: Validates by requesting a specific file from your domain. Used automatically when TLS-ALPN isn't available, such as with proxy/CDN configurations.
- ```cmd
- fly certs show example.com
- ```
- ```output
- Hostname = example.com
- Configured = true
- Issued = ecdsa, rsa
- Certificate Authority = lets_encrypt
- DNS Provider = enom
- DNS Validation Instructions = CNAME _acme-challenge.example.com => example.com.5xzw.flydns.net.
- DNS Validation Hostname = _acme-challenge.example.com
- DNS Validation Target = example.com.5xzw.flydns.net
- Source = fly
- Created At = 1m24s ago
- Status = Ready
- ```
-2. Reference the **DNS Validation Instructions** to create a `CNAME` DNS record for a subdomain, `_acme-challenge`, of your domain (**DNS Validation Hostname**) and point it at the **DNS Validation Target**.
-
-Once complete, and the updated DNS data has propagated, that domain will be queried to confirm you have control of it. Certificates will be generated and installed.
+- **DNS-01 challenge**: You manually add a DNS record to validate domain ownership. Required for wildcard certificates, or to generate certificates before directing traffic to your app.
## Other `fly cert` commands
* `fly certs list` - List the certificates associated with an app.
* `fly certs check ` - Trigger a check on the domain validation and DNS configuration for the given hostname and return results in the same format as `fly certs show`.
+* `fly certs setup ` - Shows setup instructions for configuring DNS records for an existing certificate.
* `fly certs remove ` - Remove a certificate from an app for the given hostname.
@@ -144,7 +107,7 @@ Your application code needs to know how to accept custom domains and adjust the
When users make requests, their browser sends a `Host` header you can use to alter the behavior of your application. When you run your app server on Fly.io directly, just get the contents of the `Host` header to identify a request.
-If you're running your application on another provider, you will need to create a proxy application, like NGINX to route traffic through Fly.io. Your application can then use the `X-Forwarded-Host` header to determine how to handle requests.
+If you're running your application on another provider, you will need to create a proxy application, like nginx to route traffic through Fly.io. Your application can then use the `X-Forwarded-Host` header to determine how to handle requests.
## Supported top-level domains
@@ -175,12 +138,18 @@ If you're building a platform on top of Fly.io, and you expect that your users w
### I use Cloudflare, and there seems to be a problem issuing or validating my Fly.io TLS certificate
-If you're using Cloudflare, you might be using their Universal SSL feature which inserts a TXT record of `_acme_challenge.` for your domain. This can interfere with our certificate validation/challenge and you should [disable](https://developers.cloudflare.com/ssl/edge-certificates/universal-ssl/disable-universal-ssl/#disable-universal-ssl-certificate+external) this feature.
+If you're using Cloudflare's proxying feature (orange cloud), Fly.io can generate a certificate using the HTTP-01 challenge.
+
+For this to work:
-You can then verify that the change has propagated and the TXT record is no longer present by running `dig txt _acme-challenge. +short`.
+1. **Configure DNS records properly**: Create only an AAAA record pointing to your app's IPv6 address. Do not create an A record or CNAME when using Cloudflare's proxy.
+2. **Set SSL mode**: Use "Full" or "Full (Strict)" SSL mode in Cloudflare. "Flexible" mode can cause redirect loops.
-We've also noticed that Cloudflare's "Flexible" SSL mode can cause redirect loops for applications hosted on Fly.io. Thus, we recommend setting your Cloudflare SSL mode to "Full" or "Full (Strict)".
+Alternatively, you can use the [DNS-01 challenge method](#dns-challenge) instead of the automatic validation method, though this may conflict with Cloudflare's own certificate issuance.
-## Related topics
+## Related reading
-- [Automate the certificate process for custom domains with the GraphQL API](/docs/networking/custom-domain-api/)
+- [Networking overview](/docs/networking/) Broader look at how Fly.io handles routing, DNS, IPs, and certificates.
+- [Automate the certificate process for custom domains](/docs/networking/custom-domain-api/) Use Fly.io's GraphQL API to automate cert provisioning and domain verification.
+- [TLS termination by Fly Proxy](/docs/security/tls-termination/) — How Fly.io handles HTTPS for your apps and manages TLS certificates automatically behind the scenes.
+- [Understanding Cloudflare](/docs/networking/understanding-cloudflare/) What happens when you put Fly.io behind Cloudflare, and how to avoid common pitfalls.
diff --git a/networking/custom-private-networks.html.markerb b/networking/custom-private-networks.html.markerb
index 24afb5a859..0f8a3a48d4 100644
--- a/networking/custom-private-networks.html.markerb
+++ b/networking/custom-private-networks.html.markerb
@@ -107,12 +107,14 @@ Right now the only way to get an app's network name is by listing all the apps i
"id": "682kqp63x4k9d543",
"name": "aged-cherry-366",
"machine_count": 4,
+ "volume_count": 2,
"network": "default"
},
{
"id": "704e9387kyy13xgn",
"name": "muddy-violet-667",
"machine_count": 2,
+ "volume_count": 2,
"network": "test-network"
},
...
diff --git a/networking/dynamic-request-routing.html.markerb b/networking/dynamic-request-routing.html.markerb
index 02681611b4..1bbebd1747 100644
--- a/networking/dynamic-request-routing.html.markerb
+++ b/networking/dynamic-request-routing.html.markerb
@@ -8,11 +8,11 @@ redirect_from:
- /docs/reference/dynamic-request-routing/
---
-The `fly-replay` header is a powerful feature that gives you fine-grained control over request routing in your Fly.io applications. It allows you to dynamically route requests between regions, specific Machines, or even different apps within your organization.
+The `fly-replay` feature gives you fine-grained control over request routing in your Fly.io applications. It allows you to dynamically route requests between regions, specific Machines, or even different apps within your organization.
## How fly-replay Works
-When your app adds a `fly-replay` header to a response, [Fly Proxy](/docs/reference/fly-proxy) will automatically replay the original request according to your specified routing rules. This enables advanced patterns like:
+When your app responds to a request with a `fly-replay` response, [Fly Proxy](/docs/reference/fly-proxy) will automatically replay the original request according to your specified routing rules. This enables advanced patterns like:
- Routing write operations to primary database regions
- Load balancing between specific Machines
@@ -21,25 +21,36 @@ When your app adds a `fly-replay` header to a response, [Fly Proxy](/docs/refere
<%= youtube "https://www.youtube.com/watch?v=riCh9Xeuf0s" %>
-## Header Fields Reference
+## Replay Header Format
-The `fly-replay` header accepts the following fields:
+Your app can add a `fly-replay` header to its response. The `fly-replay` header accepts the following fields:
|Field |Description |
|---|---|
-|`region` | The 3-letter code for the [region](/docs/reference/regions/) to route the request to |
+|`region` | The region(s) to route the request to. Accepts comma-separated list of [region codes](/docs/reference/regions/) |
|`instance` | The ID of a specific Machine to route to |
+|`prefer_instance` | The ID of a specific Machine to route to, if possible |
|`app` | The name of another app (in same organization) to route to |
|`state` | Optional string included in `fly-replay-src` header on replay |
|`elsewhere` | If `true`, excludes responding Machine from next load-balance |
### Example Usage
-Route to specific region:
+Route to a specific region:
```
fly-replay: region=sjc
```
+Route to one of the given regions, in order of preference:
+```
+fly-replay: region="iad,ord,us,na"
+```
+
+Route to a preferred region, or fallback to any available machine:
+```
+fly-replay: region="sjc,any"
+```
+
Route to specific Machine:
```
fly-replay: instance=00bb33ff
@@ -52,9 +63,208 @@ fly-replay: app=app-in-same-org
You can combine multiple fields:
```
-fly-replay: region=sjc;app=app-in-same-org
+fly-replay: region="sjc,any";app=app-in-same-org
```
+
+**Note**: A comma-separated list of regions must be quoted.
+
+
+### Geographic groups and aliases
+
+When replaying to a region, you can use geographic aliases like `us`, `eu`, or `sa` to target a broader area.
+
+| Alias | Area|
+|---------|------------------------|
+| `apac` | Asia-Pacific |
+| `eu` | Europe |
+| `na` | North America |
+| `sa` | South America |
+| `us`, `usa` | United States |
+| `any` | Earth |
+
+## Replay JSON Format
+
+Your app can set the response content-type to `application/vnd.fly.replay+json` and include replay instructions in the response body.
+For a complex replay the JSON body is easier to compose than the header format, and also supports more functionality.
+
+### JSON Structure
+
+The `application/vnd.fly.replay+json` replay body accepts the following fields:
+
+|Field |Description |
+|---|---|
+|`region` | The region(s) to route the request to. Accepts comma-separated list of [region codes](/docs/reference/regions/) |
+|`instance` | The ID of a specific Machine to route to |
+|`prefer_instance` | The ID of a specific Machine to route to, if possible |
+|`app` | The name of another app (in same organization) to route to |
+|`state` | Optional string included in `fly-replay-src` header on replay |
+|`elsewhere` | If `true`, excludes responding Machine from next load-balance |
+|`transform.path` | Rewrite the path and query parameters of the request |
+|`transform.delete_headers` | Delete headers from the request, hiding them from the replay target |
+|`transform.set_headers` | Set new headers on the request, overwriting headers of the same name |
+|`cache.prefix` | Cache the replay for matching requests (see [Replay Caching](#replay-caching)) |
+|`cache.ttl` | Cache the replay for this many seconds (see [Replay Caching](#replay-caching)) |
+|`cache.invalidate` | Invalidate the cache for the current route (see [Replay Caching](#replay-caching)) |
+
+### Example Usage
+
+Route to another app, and modify the request:
+
+```json
+{
+ "app": "target-app",
+ "region": "iad,us",
+ "transform": {
+ "path": "/new/path?param=value",
+ "delete_headers": ["x-unwanted-header", "cookie"],
+ "set_headers": [
+ { "name": "x-custom-header", "value": "new-value" },
+ { "name": "authorization", "value": "Bearer token123" }
+ ]
+ }
+}
+```
+
+## Replay Caching
+
+Replay caching allows Fly Proxy to remember and reuse replay decisions, reducing both load on your application and the latency of replayed requests. There are two types of replay caching:
+
+- **Path-based**: Cache replays for specific URL paths
+- **Session-based**: Cache replays for specific cookie or header values
+
+
+**Note**: Replay caching is an optimization, not a guarantee. Your app should _not_ depend on this mechanism to function.
+The app issuing `fly-replay` still serves as the ultimate source of truth, and we may decide to consult that app at any moment even if a replay cache has previously been set.
+To ensure reliable operation, the app issuing `fly-replay` should still have multiple instances deployed in multiple regions.
+
+
+### Path-based Replay Caching
+
+Replay caching for paths is configured per-request during a replay. To cache a replay for a path:
+
+- Issue a `fly-replay` header as usual
+- Set `fly-replay-cache: /some/path/*`
+ - This needs to be a path-matching pattern, and implicitly ends with a wildcard `/*`. This is where the cached replay is applied.
+ - The cached replay is unique to the request domain. The domain may also be set explicitly: `example.com/some/path/*`
+ - The domain part should not include ports; use `example.com`, not `example.com:80`.
+ - The pattern must also match the current request.
+- Set `fly-replay-cache-ttl-secs: number_of_seconds`
+
+For apps using the JSON format, specify a `cache` object as part of the replay:
+
+```json
+{
+ "app": "target-app",
+ "cache": {
+ "prefix": "/some/path/*",
+ "ttl": 60
+ }
+}
+```
+
+### Session-based Replay Caching
+
+If your app's sessions are identified by a cookie or authorization header, and should consistently route to the same target, Fly Proxy can cache that replay.
+
+Unlike path-based caching (which requires your app to return `fly-replay-cache` headers), session-based caching is configured in your `fly.toml`. When your app issues a `fly-replay` response for a request with a matching cookie or header, Fly Proxy will cache that replay decision and automatically apply it to subsequent requests with the same session identifier.
+
+#### Configuration
+
+Configure session-based replay caching in your `fly.toml`. This can be set under `http_service.http_options` or `services.ports.http_options`:
+
+```toml
+[http_service]
+ internal_port = 8080
+ force_https = true
+
+ [[http_service.http_options.replay_cache]]
+ path_prefix = "/"
+ ttl_seconds = 300
+ type = "cookie"
+ name = "session_id"
+
+ [[http_service.http_options.replay_cache]]
+ path_prefix = "/api"
+ ttl_seconds = 600
+ type = "header"
+ name = "Authorization"
+```
+
+In this example:
+- Replays for requests to `/` (and its subpaths) are cached for 5 minutes based on the `session_id` cookie value
+- Replays for requests to `/api` (and its subpaths) are cached for 10 minutes based on the `Authorization` header value
+- When multiple rules match, the longest matching path takes precedence
+
+If your app accepts requests for multiple hostnames, you can narrow the configuration by specifying a hostname in `path_prefix`:
+
+```toml
+ [[http_service.http_options.replay_cache]]
+ path_prefix = "api.example.com/api"
+```
+
+Caches are not shared between hostnames, even when `path_prefix` doesn't specify a hostname. Each hostname maintains its own cache.
+
+For more details on configuration options, see the [fly.toml configuration reference](/docs/reference/configuration/#http_service-http_options-replay_cache).
+
+### Invalidating the Replay Cache
+
+If the replay target does eventually change, the replay target may proactively invalidate the cache by:
+- Issuing a `fly-replay` back to the origin
+- Setting `fly-replay-cache: invalidate`
+
+Or, using the JSON format:
+
+```json
+{
+ "app": "origin-app",
+ "cache": {
+ "invalidate": true
+ }
+}
+```
+
+This works for invalidating both the path-based cache, and the session-based cache.
+
+### Bypassing Replay Cache
+
+Sometimes, you might want a client to bypass a cached replay without actually invalidating the cache.
+For example, you might have an API endpoint that _mostly_ needs to be replayed to a "leader" instance. But depending on application logic, it could sometimes be handled by any instance.
+
+The recommended approach is to clearly separate endpoints that require replay from those that don't.
+However, when that is not possible, the replaying app can be configured to allow bypass.
+
+For path-based replay caching, additionally set the header:
+
+```
+fly-replay-cache-allow-bypass: yes
+```
+
+or set the JSON field `"allow_bypass": true`.
+
+For session-based replay caching, set [allow_bypass](/docs/reference/configuration/#http_service-http_options-replay_cache-allow_bypass) to `true` in your `fly.toml` configuration for your `replay_cache` rule.
+
+Then, when your client makes a request, it can set the header:
+
+```
+fly-replay-cache-control: skip
+```
+
+to skip any cached replay that might be present.
+
+
+**Note**: This behavior is opt-in because enabling it by default could make your app vulnerable to malicious clients creating unexpected load on the replaying machine.
+
+
+### Was my request served by the cache?
+
+If your app needs to know whether a request was served from the replay cache, it can check the fly-replay-cache-status header. This header is sent to the replay _target_ app or machine.
+
+- Absent: the request was not replayed and was delivered directly to the app or machine.
+- `miss`: the request didn't use replay cache; it first hit another machine and got replayed here.
+- `hit`: the request did use the replay cache! It never reached the source app or machine and was redirected straight to the target.
+- `bypass`: even though the cache might have been set, it was explicitly skipped by setting the `fly-replay-cache-control: skip` header.
+
## Implementation Details
### Requirements and Limitations
@@ -67,6 +277,13 @@ For large uploads that exceed the 1MB limit, consider:
- Using direct-to-storage uploads where possible
- Using the [fly-prefer-region](#the-fly-prefer-region-request-header) header instead
+For `fly-replay-cache`, the following limitations apply:
+- The `state` field cannot be set in the `fly-replay` intended to be cached
+- Transformations for the headers or path cannot be defined.
+- The TTL needs to be a minimum of 10 seconds
+- Only one step of lookup is performed in the cache; as such, if the target app issues another `fly-replay-cache`, the caching behavior in this case is undefined
+- The `fly-replay-src` header (described below) will _not_ be set for requests replayed through the cache
+
### The fly-replay-src Header
When a request is replayed, Fly Proxy adds a `fly-replay-src` header containing metadata about the original request:
@@ -80,6 +297,18 @@ When a request is replayed, Fly Proxy adds a `fly-replay-src` header containing
This header is useful for tracking request paths and implementing consistency patterns. See the [official Ruby client](https://github.com/superfly/fly-ruby/blob/main/lib/fly-ruby/regional_database.rb#L74-L76+external) for an example of using these fields to prevent read-your-write inconsistency.
+This header is not set when the request is replayed through a cached `fly-replay` entry (`fly-replay-cache`).
+
+### The fly-preferred-instance-unavailable Header
+
+If you replay with `prefer_instance` set, Fly Proxy will attempt to route to this Machine. This may not happen for a number of reasons, for example the Machine may not be found, or found but at its configured [hard_limit](/docs/reference/load-balancing/#load).
+
+In these cases, the request will be delivered to a different Machine that matches the remaining fields in your replay. Along with the other Fly.io-specific headers, a `fly-preferred-instance-unavailable` header will be set containing the ID of the instance that could not be reached.
+
+### Web Socket Considerations
+
+It is worth noting that an application returning `fly-replay` headers should not negotiate a web socket upgrade itself. Some frameworks automatically handle this process. Instead, the application or instance receiving the requests should handle the upgrade.
+
## Alternative Routing Headers
For cases where `fly-replay` isn't suitable, Fly.io provides two alternative request headers:
@@ -90,7 +319,23 @@ For cases where `fly-replay` isn't suitable, Fly.io provides two alternative req
fly-prefer-region: ams
```
-Attempts to route directly to a specific region. Falls back to nearest region with healthy Machines if target region is unavailable. Useful for large uploads that can't be replayed.
+Attempts to route directly to specific region(s). You can specify multiple regions as comma-separated values for fallback preferences:
+
+```
+fly-prefer-region: iad,ord,us
+```
+
+Falls back to the next region in the list, or to the nearest region with healthy Machines if none of the specified regions are available. Useful for large uploads that can't be replayed.
+
+### The fly-prefer-instance-id Header
+
+```
+fly-prefer-instance-id: 90801679a10038
+```
+
+Attempts to route to a specific Machine. Falls back to any matching Machine if the target Machine cannot be found, or is unable to accept requests.
+
+If the request is delivered to a different Machine, the [fly-preferred-instance-unavailable](#the-fly-preferred-instance-unavailable-header) request header will be set.
### The fly-force-instance-id Header
@@ -98,7 +343,7 @@ Attempts to route directly to a specific region. Falls back to nearest region wi
fly-force-instance-id: 90801679a10038
```
-Forces routing to a specific Machine. No fallback if Machine is unavailable.
+Forces routing to a specific Machine. The Fly Proxy will attempt to reach the Machine multiple times if the Machine is unhealthy. No fallback if Machine is ultimately unavailable.
**Note**: Get Machine IDs using `fly status` or `fly Machines list`.
diff --git a/networking/egress-ips.html.md b/networking/egress-ips.html.md
new file mode 100644
index 0000000000..e022e956ae
--- /dev/null
+++ b/networking/egress-ips.html.md
@@ -0,0 +1,106 @@
+---
+title: "Egress IP addresses"
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-10-02
+---
+
+## Overview
+
+- By default, outbound (egress) IPs from Fly Machines are **unstable** and may change.
+- You can allocate **static egress IPs** per machine (both IPv4 and IPv6) via `fly machine egress-ip`.
+- Static egress IPs come with trade-offs: cost, binding to machine lifecycle, and deployment quirks.
+- A common workaround is to front outbound traffic through a **proxy** app that _does_ have static egress IPs.
+- App-scoped egress IPs are in development and may simplify this in the future.
+
+---
+
+## Why Egress IPs Matter
+
+Some external services—APIs, databases, payment providers—require allowlisting source IPs. Without static egress IPs, outbound IPs from Fly machines may change due to machine lifecycle or infrastructure changes.
+
+- Machines often egress over IPv6 when the destination has an AAAA record and the application prefers it. For example, `curl` will try IPv6 first if available, then fall back to IPv4 if needed. Fly doesn’t force IPv6, but many apps will use it when it's available.
+- IPv4 traffic is NAT'd and may vary. This means the source IP address is rewritten by the host, and which IP you get can change depending on where the machine runs or is restarted.
+- You need static egress if you're allowlisting IPs with third-party services.
+
+---
+
+## Static Egress IPs (Machine-Scoped)
+
+### Allocate a Static Egress IP
+
+```bash
+fly machine egress-ip allocate
--app
+```
+
+- This assigns a stable IPv4 + IPv6 pair to the specified machine.
+
+### View and Manage
+
+```bash
+fly machine egress-ip list --app
+fly machine egress-ip release --app
+```
+
+### Caveats
+
+Static egress IPs are **per-machine**, not per-app.
+
+- IPs are released when a machine is destroyed.
+- IPs don’t automatically transfer across deploys.
+- Blue/green deployments will replace machines—and their IPs.
+- Deployment-time jobs may bypass egress routing.
+- Extra latency and connectivity issues are possible in some regions.
+
+
+Static egress IPs are billed per hour per machine.
+
+
+---
+
+## The Proxy Pattern
+
+To avoid assigning static IPs to every machine, route traffic through a shared proxy app.
+
+### How It Works
+
+1. Deploy a small Fly app (e.g. `egress-proxy`) with static egress IPs.
+1. Run a forward HTTP/HTTPS proxy on it.
+1. Set `http_proxy` / `https_proxy` env vars in consuming apps.
+1. Outbound traffic from those apps will route through the proxy.
+
+### Benefits
+
+- Fewer IPs to manage.
+- Primary app machines can be ephemeral.
+- Centralize allowlisting.
+
+### Downsides
+
+- Primarily supports HTTP/S traffic. Other protocols (like raw TCP or Postgres) may be possible with extra work, such as using SOCKS5 proxies, `haproxy` in TCP mode, or `socat`, but those setups are more complex and outside the scope of this guide.
+- Adds some latency (~100ms typical).
+- Requires maintaining a separate proxy app.
+
+
+Example implementation: [fly-apps/fly-fixed-egress-ip-proxy](https://github.com/fly-apps/fly-fixed-egress-ip-proxy)
+
+
+---
+
+## Best Practices
+
+- Use static egress only when required.
+- Prefer the proxy pattern for maintainability.
+- Test connectivity after assigning egress IPs.
+- Avoid destroying machines unnecessarily.
+- Monitor for failures during deploy-time migrations.
+
+---
+
+## Future Work
+
+App-scoped egress IPs are in development. These will simplify routing and avoid per-machine binding.
+
+Until then, static IPs and proxy patterns remain the best tools available.
+
diff --git a/networking/index.html.markerb b/networking/index.html.markerb
index c561926a51..f313618ef1 100644
--- a/networking/index.html.markerb
+++ b/networking/index.html.markerb
@@ -19,11 +19,15 @@ Networking on Fly.io.
- **[Custom Private Networks](/docs/networking/custom-private-networks):** Isolate users, data, and code on custom private networks.
- **[Flycast - Private Fly Proxy Services](/docs/networking/flycast):** Route requests to private apps through Fly Proxy to take advantage of features like load balancing and autostop/autostart based on traffic.
+
+- **[Egress IP Addresses](/docs/networking/egress-ips/):** How to get stable outbound IPs from your Fly apps using machine-scoped assignments or a shared proxy setup.
- **[Dynamic request routing](/docs/networking/dynamic-request-routing/):** Use Fly.io request and response headers to customize request routing to regions, apps, and even specific Machines.
- **[Custom domains](/docs/networking/custom-domain/):** Add a custom domain for your app and troubleshoot certificate creation.
+- **[Understanding Cloudflare](/docs/networking/understanding-cloudflare/):** How to use Cloudflare on Fly.io.
+
- **[Automate the certificate process with the GraphQL API](/docs/networking/custom-domain-api):** Issue multiple certificates automatically for custom domains with the GraphQL API.
- **[HTTP request headers](/docs/networking/request-headers/):** Fly.io-specific and standard HTTP headers added by the HTTP connection handler.
diff --git a/networking/private-networking.html.md b/networking/private-networking.html.md
index 275fbe5a60..14ac87933f 100644
--- a/networking/private-networking.html.md
+++ b/networking/private-networking.html.md
@@ -17,35 +17,44 @@ You can connect apps running outside of Fly.io to your 6PN using WireGuard. You
You can use `.internal` domains to connect your app to databases, API servers, or other apps in your 6PN. If you don't need the granular subdomains and routing available with `.internal`, and you want to use Fly Proxy features for your internal apps, then you should use [Flycast](/docs/networking/flycast/) instead.
-A Fly Machine is configured to resolve domain names with a custom DNS server from the Fly Platform. This DNS server can resolve arbitrary DNS queries, so you can look up `google.com` with it. But it’s also aware of 6PN addresses, and will let you look up 6PN addresses for other apps in your organization. Those addresses live under the custom top-level domain `.internal`.
+A Fly Machine is configured to resolve domain names with a custom DNS server from the Fly Platform. This DNS server can resolve arbitrary DNS queries, so you can look up `google.com` with it. But it’s also aware of 6PN addresses, and will let you look up 6PN addresses for other apps in your organization. Those addresses live under the custom top-level domain `.internal`.
Underneath `.internal` there are second-level domains for every app in your Fly organization. For example, if your app is in an organization with another app called `my-app-name`, then there will be a AAAA record at `my-app-name.internal`. The AAAA record will contain all the 6PN addresses of the started Fly Machines that belong to the `my-app-name` Fly App. Note that different libraries and tools will use multi-address AAAA records differently; most will only use the first address that is returned, but others might round-robin between entries for every request -- if you'd like to know more, consult the documentation for the library or tool you are using for DNS lookup.
-
-**Important:** All queries to Fly.io `.internal` domains only return information for started (running) Machines. Any stopped Machines, including those autostopped by Fly Proxy, are not included in the response to the DNS query.
-
-
Each `.internal` domain has further subdomains which can be used to return a more precise subset of the started Machines in that app. For example, you can add a region name qualifier to return the 6PN addresses of an app's Machines in a specific region: `iad.my-app-name.internal`. Querying this domain returns the 6PN addresses of `my-app-name` Machines in the `iad` region.
Some `.internal` domains do not contain an AAAA record, but instead contain a TXT record with Machine, app, or region information. For example, if you request the TXT records using `regions.my-app-name.internal`, then you'll get back a comma-separated list of regions that `my-app-name` is deployed in. And you can discover all the apps in the organization by requesting the TXT records associated with `_apps.internal`. This will return a comma-separated list of the app names.
-The following table lists the available `.internal` domains:
-
-| Name | AAAA | TXT |
-| -- | --- | -- |
-|`.internal`|6PN addresses of all Machines in any region for the app|none
-|`top.nearest.of..internal`|6PN addresses of top _number_ closest Machines for the app|none
-|`.vm..internal`|6PN address of a specific Machine for the app|none
-|`vms..internal`|none|comma-separated list of Machine ID and region name for the app
-|`.process..internal`|6PN addresses of Machines in process group for the app|none
-|`..internal`|6PN addresses of Machines in region for the app|none
-|`global..internal`|alias for `.internal`|none
-|`regions..internal`|none|comma-separated list of region names where Machines are deployed for app|
-|`..kv._metadata..internal`|6PN addresses of Machines with matching [metadata](https://community.fly.io/t/dynamic-machine-metadata/13115)|none|
-|`_apps.internal`|none|comma-separated list of the names of all apps in current organization|
-|`_peer.internal`|none|comma-separated list of the names of all WireGuard peers in current organization|
-|`._peer.internal`|6PN address of peer|none|
-|`_instances.internal`|none|comma-separated list of Machine ID, app name, 6PN address, and region for all Machines in current organization|
+
+**Important:** All AAAA queries to Fly.io `.internal` domains only return 6PN information for started (running) Machines. Any stopped Machines, including those autostopped by Fly Proxy, are not included in the response to the DNS query.
+
+
+The following table lists the available `.internal` domains for AAAA queries:
+
+| Name | AAAA Response |
+| -- | --- |
+|`.internal`|6PN addresses of all Machines in any region for the app|
+|`top.nearest.of..internal`|6PN addresses of top _number_ closest Machines for the app|
+|`.vm..internal`|6PN address of a specific Machine for the app|
+|`.process..internal`|6PN addresses of Machines in process group for the app|
+|`..internal`|6PN addresses of Machines in region for the app|
+|`global..internal`|alias for `.internal`|
+|`..kv._metadata..internal`|6PN addresses of Machines with matching [metadata](https://community.fly.io/t/dynamic-machine-metadata/13115)|
+|`._peer.internal`|6PN address of peer|
+
+The following table lists the available `.internal` domains for TXT queries:
+
+| Name | TXT Response |
+| -- | -- |
+|`vms..internal`|comma-separated list of Machine ID and region name for started app Machines|
+|`all.vms..internal`|comma-separated list of Machine ID and region name for all deployed app Machines|
+|`regions..internal`|comma-separated list of region names where Machines are started for app|
+|`all.regions..internal`|comma-separated list of region names where Machines are deployed for app|
+|`_apps.internal`|comma-separated list of the names of all apps in current organization|
+|`_peer.internal`|comma-separated list of the names of all WireGuard peers in current organization|
+|`_instances.internal`|comma-separated list of Machine ID, app name, 6PN address, and region for all started Machines in current organization|
+|`all._instances.internal`|comma-separated list of Machine ID, app name, 6PN address, and region for all deployed Machines in current organization|
+
See the [fly-examples/privatenet](https://github.com/fly-apps/privatenet+external) repo for examples that use the `.internal` domains.
diff --git a/networking/services.html.markerb b/networking/services.html.markerb
index 8167d522db..8ad57a4d74 100644
--- a/networking/services.html.markerb
+++ b/networking/services.html.markerb
@@ -121,11 +121,24 @@ The HTTP handler adds a number of standard HTTP headers to requests, and a few F
| `X-Forwarded-Port` | Original connection port, header may be set by client |
| `Fly-Forwarded-Port` | Original connection port, always set by Fly.io |
| `Fly-Region` | Original incoming connection region |
+| `Fly-Preferred-Instance-Unavailable` | The ID of a preferred instance that the proxy could not route to |
-You can set a preference on HTTP requests for which region you would like to connect to:
+You can set a preference on HTTP requests for which region you would like to connect to. You can specify a single region or multiple regions in order of preference:
```
-Fly-Prefer-Region: region-code
+Fly-Prefer-Region: iad
+```
+
+Or with multiple preferences (tries regions in order):
+
+```
+Fly-Prefer-Region: iad,ord,us,na
+```
+
+You can set a preference for a specific machine instance:
+
+```
+Fly-Prefer-Instance-Id: instance-number
```
You can force routing to a specific machine instance:
@@ -212,9 +225,11 @@ The `force_https` configuration option automatically redirects HTTP to HTTPS. Wh
Fly Machines have IPv6 addresses from which they make requests to the wider internet without going through the Fly Proxy.
-We don’t offer static IPs or regional IP ranges, and we discourage the use of our outbound IPs to bypass firewalls. A Machine's outbound IP is liable to change without notice. This shouldn't be a daily occurrence, but it will happen if a Machine is moved for whatever reason, such as a load-balancing of Fly Machines between servers in one region.
+By default, we don’t offer static IPs or regional IP ranges, and we discourage the use of our outbound IPs to bypass firewalls. A Machine's outbound IP is liable to change without notice. This shouldn't be a daily occurrence, but it will happen if a Machine is moved for whatever reason, such as a load-balancing of Fly Machines between servers in one region.
+
+If you do need a static outbound IP, you can assign one on a per-machine basis. You can allocate one to a machine with `fly machine egress-ip allocate . This will assign both an IPv4 and IPv6 address to the machine. Like dedicated IPv4 addresses, these are [billed](/docs/about/pricing/#static-machine-ip) monthly.
-If you depend on knowing this address for some reason, it's up to you to monitor it for changes.
+You can also use the `fly machine egress-ip list` command to see all the IPs assigned to your machines, and release IPs with `fly machine egress-ip release `.
### Find your Machine's outbound IP
@@ -252,3 +267,10 @@ You may have to install software on the Machine to use `dig` or `curl`. On Debia
## Default-Deny security policy
All requests to Fly.io apps are routed to our edge servers, where we use our memory-safe Rust proxy to direct traffic. The proxy listens on all ports and all Anycast addresses, so ports appear to be 'open' from the wider internet. However, nothing on your app is exposed unless you expose it via [services](/docs/networking/app-services/). You’re locked down by default.
+
+## Related reading
+
+- [Connect to an App Service](/docs/networking/app-services/) How to expose your Fly App’s processes publicly or privately through services.
+- [Dynamic Request Routing with fly‑replay](/docs/networking/dynamic-request-routing/) How to route requests between regions, apps, or machines using Fly‑specific headers.
+- [Connect Your Private Network with WireGuard](/docs/blueprints/connect-private-network-wireguard/) Set up secure access between Fly.io and your private infrastructure using WireGuard tunnels.
+- [Private Networking](/docs/networking/private-networking/) How services communicate securely over Fly.io’s private network without going through the public internet.
diff --git a/networking/understanding-cloudflare.html.md b/networking/understanding-cloudflare.html.md
new file mode 100644
index 0000000000..f7183f6ee3
--- /dev/null
+++ b/networking/understanding-cloudflare.html.md
@@ -0,0 +1,102 @@
+---
+title: "Understanding Cloudflare"
+layout: docs
+nav: firecracker
+author: kaelyn
+date: 2025-07-16
+---
+
+Many Fly.io apps use Cloudflare—sometimes just for DNS, sometimes with proxying enabled, and sometimes for both. This guide covers the supported configurations, how to set them up, and what to watch out for when using Cloudflare with Fly.io.
+
+## DNS-only setup
+
+This is the simplest and most reliable way to use Cloudflare with Fly.io. To configure a DNS-only setup:
+
+1. Point your domain to your Fly.io app using an `AAAA` record for the IPv6 address and an `A` record for the IPv4 address.
+2. Alternatively, use a `CNAME` record pointing to your app's unique target, shown in your certificate setup instructions.
+3. Disable the Cloudflare proxy (select "grey cloud") for these records.
+4. SSL certificates will be handled by Fly.io automatically using Let's Encrypt.
+
+## CDN proxy setup ("orange cloud")
+
+Enabling Cloudflare's proxy gives you caching and DDoS protection, but it also changes how SSL certificates work. Cloudflare terminates TLS traffic, which interferes with Fly.io's default TLS-ALPN-01 certificate issuance process.
+
+The recommended approach for using Cloudflare's CDN proxy is to configure it to forward HTTP requests, which allows HTTP-01 challenges to work properly. To configure a CDN proxy setup:
+
+1. Create an `AAAA` record only pointing to your Fly.io app's IPv6 address.
+2. Do not add `A` or `CNAME` records. If you previously had an `A` record pointing elsewhere (such as a legacy server or placeholder), remove it, even if the correct `AAAA` record is present. Having any `A` record alongside the `AAAA` can confuse Let’s Encrypt validation and prevent certificate renewal.
+3. Enable the Cloudflare proxy (orange cloud).
+4. Set SSL mode in Cloudflare to Full (strict).
+5. Enable Always Use HTTPS in Cloudflare.
+
+
+**Important:** This setup allows Fly.io to handle HTTP-01 validation and issue certificates automatically.
+
+
+
+### Using the DNS-01 challenge (manual certificate setup)
+
+If the HTTP-01 challenge doesn't work for your setup, you can fall back to using a DNS-01 challenge to manually issue a certificate.
+
+To do this:
+1. Use the Fly.io dashboard or run:
+
+```bash
+fly certs create
+```
+
+2. Add the required TXT records to Cloudflare when prompted.
+3. The certificate will issue once DNS propagation is complete.
+
+### How Fly.io handles TLS and certificate management
+
+TLS certificates are provisioned automatically using Let’s Encrypt. We handle renewals in advance and manage rate limits carefully per hostname, so you don’t need to worry about expiration dates or throttling.
+
+We don’t currently support bringing your own ACME provider like ZeroSSL or SSL.com into our provisioning flow. If you prefer to terminate TLS yourself and handle your own ACME HTTP challenges, you can do that by passing TCP through to your Fly app. The Fly Proxy won’t interfere with these challenges, and there's no IPv6 requirement if you're managing this independently.
+
+Both approaches are valid, but we recommend using the platform's built-in TLS termination and certificate management, especially as certificate validity periods get shorter.
+
+## Common issues to watch for
+
+- Cloudflare's Universal SSL may interfere with DNS-01 challenges. Disable it or use HTTP-01 instead to avoid this.
+- Check that your domain allows Let's Encrypt with a CAA record like:
+
+```
+ example.com. CAA 0 issue "letsencrypt.org"
+```
+
+- Only one application should manage certificates for a domain. Using more than one can cause conflicts.
+- Let's Encrypt has limits. Check the certificate status tab in the Fly.io dashboard if issuance fails.
+
+
+**Cloudflare Universal SSL Ghost Records Issue:** If you're using Cloudflare and Let's Encrypt can't issue a certificate, you may be encountering phantom _acme-challenge TXT records. This happens when Cloudflare's Universal SSL automatically inserts hidden ACME challenge TXT records that don't appear in your DNS dashboard but show up when you run `dig TXT _acme-challenge.yourdomain.com`. These ghost records interfere with Let's Encrypt's validation process.
+
+To resolve this:
+1. Go to Edge Certificates in Cloudflare's SSL/TLS settings and disable Universal SSL.
+2. Purge cache ("Purge Everything" in Cloudflare) and/or enable Development Mode.
+3. Wait several minutes for DNS cache to clear, then confirm with `dig` that only your intended TXT record is present.
+4. Retry certificate issuance from Fly.io.
+
+
+## Tools for debugging
+
+These tools can help when diagnosing certificate or DNS issues:
+
+- [crt.sh](https://crt.sh/): Check issued certificates.
+- [DNSChecker](https://dnschecker.org/): Confirm DNS propagation.
+- [Let's Debug](https://letsdebug.net/): Analyze certificate validation issues.
+- dig: Inspect DNS records from the command line.
+
+For example:
+
+```bash
+dig AAAA your-app.example.com
+
+dig TXT _acme-challenge.your-app.example.com
+```
+
+## Related topics
+
+- [Custom domain guide](https://fly.io/docs/networking/custom-domain/)
+- [Custom domain API reference](https://fly.io/docs/networking/custom-domain-api/)
+- [flyctl certs commands](https://fly.io/docs/flyctl/certs/)
diff --git a/partials/_accordion_nav.html.erb b/partials/_accordion_nav.html.erb
index 56c82ac5cc..627f83a1ff 100644
--- a/partials/_accordion_nav.html.erb
+++ b/partials/_accordion_nav.html.erb
@@ -2,14 +2,16 @@
def section_open?(section)
(!section[:path].nil? && current_page?(section[:path])) ||
(!section[:open].nil? && section[:open]) ||
- section[:links].any? { |page| current_page?(page[:path]) || (page[:links] && section_open?(page)) }
+ (section[:links] && section[:links].any? { |page| current_page?(page[:path]) || (page[:links] && section_open?(page)) })
end
nav.each { |nav| nav[:open] = section_open?(nav) unless nav[:title].nil? }
nav.each do |nav|
%>
- <% if nav[:text] %>
+ <% if nav[:links].nil? %>
+ <%= nav_link nav[:title], nav[:path], class: "title-link" %>
+ <% elsif nav[:text] %>
<%= nav_link nav[:text], nav[:path], class: "#{nav[:type]}-link" %>
<% elsif nav[:accordion] == false %>
@@ -18,7 +20,7 @@
- <% nav[:links].each do |page| %>
+ <% nav[:links]&.each do |page| %>
<%= nav_link page[:text], page[:path], class: "subpage-link" %>
<% end %>
@@ -32,13 +34,16 @@
<% else %>
<%= nav[:title] %>
<% end %>
-
- Toggle <%= nav[:title] %> section
-
+ <%# Only show the accordion trigger if there are real subpages %>
+ <% if nav[:links] && nav[:links].size > 0 %>
+
+ Toggle <%= nav[:title] %> section
+
+ <% end %>
- <% nav[:links].each do |page| %>
+ <% nav[:links]&.each do |page| %>
<% if page[:links] %>
">
@@ -54,7 +59,7 @@
- <% page[:links].each do |subpage| %>
+ <% page[:links]&.each do |subpage| %>
<% if subpage[:type] == "flyctl" %>
<%= flyctl_nav_link subpage[:text], subpage[:path] %>
diff --git a/partials/_apps_nav.html.erb b/partials/_apps_nav.html.erb
index 73b656fecf..3f2b0f6669 100644
--- a/partials/_apps_nav.html.erb
+++ b/partials/_apps_nav.html.erb
@@ -48,6 +48,7 @@
{ text: "Going to production", path: "/docs/apps/going-to-production/" },
{ text: "App availability and resiliency", path: "/docs/apps/app-availability/" },
{ text: "Fine-tune and benchmark apps", path: "/docs/apps/fine-tune-apps/" },
+ { text: "App handover guide", path: "/docs/apps/app-handover-guide/" },
{ text: "Concurrency settings", path: "/docs/apps/concurrency/" }
]
},
diff --git a/partials/_firecracker_nav.html.erb b/partials/_firecracker_nav.html.erb
index 536d92508d..18ea77f4fc 100644
--- a/partials/_firecracker_nav.html.erb
+++ b/partials/_firecracker_nav.html.erb
@@ -1,10 +1,8 @@
-<%= partial "/docs/partials/back_to_docs" unless current_page?('/docs') %>
-
<%
@nav = [
{
title: "Getting Started",
- path: "/docs/getting-started/",
+ path: "/docs/getting-started/launch",
open: current_page?("/docs"),
links: [
{ text: "Quickstart: Launch your app", path: "/docs/getting-started/launch/" },
@@ -16,16 +14,16 @@
]
},
{
- title: "Fly.io Blueprints",
+ title: "Guides (Blueprints)",
path: "/docs/blueprints/",
open: false,
- links: site.get("docs/blueprints").children.reject{ |page| page.data.status == "alpha" || page.data.published == false }.map do |page|
- { text: page.data.title, path: page.request_path }
- end
+ links: [
+ { text: "Guides Overview", path: "/docs/blueprints/" }
+ ]
},
{
title: "Apps on Fly.io",
- path: "/docs/apps/",
+ path: "/docs/apps/overview",
open: false,
links: [
{ text: "Fly Apps Overview", path: "/docs/apps/overview/" },
@@ -54,7 +52,7 @@
},
{
title: "Fly Machines",
- path: "/docs/machines/",
+ path: "/docs/machines/overview",
open: false,
links: [
{ text: "Introduction to Fly Machines", path: "/docs/machines/overview/" },
@@ -65,12 +63,26 @@
{ text: "Machine Restart Policy", path: "/docs/machines/guides-examples/machine-restart-policy/" },
{ text: "Machine States", path: "/docs/machines/machine-states/" },
{ text: "Run User Code on Fly Machines", path: "/docs/machines/guides-examples/functions-with-machines/" },
+ { text: "One App Per Customer - Why?", path: "/docs/machines/guides-examples/one-app-per-user-why/" },
{ text: "The Machine Runtime Environment", path: "/docs/machines/runtime-environment/" }
]
},
+ {
+ title: "Managed Postgres",
+ path: "/docs/mpg",
+ open: false,
+ links: [
+ { text: "Create and Connect to a Managed Postgres Cluster", path: "/docs/mpg/create-and-connect/" },
+ { text: "Cluster Configuration Options", path: "/docs/mpg/configuration/" },
+ { text: "Phoenix with Managed Postgres", path: "/docs/mpg/guides-examples/phoenix-guide/" },
+ { text: "Monitoring and Metrics", path: "/docs/mpg/metrics/" },
+ { text: "Import data from another postgres cluster", path: "/docs/mpg/import/" },
+ { text: "Supported Postgres Extensions", path: "/docs/mpg/extensions/" }
+ ]
+ },
{
title: "Fly GPUs",
- path: "/docs/gpus/",
+ path: "/docs/gpus/gpu-quickstart",
open: false,
links: [
{ text: "GPU Quickstart", path: "/docs/gpus/gpu-quickstart/" },
@@ -83,17 +95,14 @@
path: "/docs/database-storage-guides/",
open: false,
links: [
+ { text: "Fly Managed Postgres", path: "/docs/mpg/" },
{ text: "Tigris Object Storage", path: "/docs/tigris/" },
- { text: "Fly Postgres", path: "/docs/postgres/" },
- { text: "SQLite & LiteFS", path: "/docs/litefs/" },
- { text: "Upstash for Redis®", path: "/docs/upstash/redis/" },
- { text: "Upstash Kafka", path: "/docs/upstash/kafka/" },
- { text: "Upstash Vector", path: "/docs/upstash/vector/" }
+ { text: "Upstash for Redis®", path: "/docs/upstash/redis/" }
]
},
{
title: "Fly Volumes",
- path: "/docs/volumes/",
+ path: "/docs/volumes/overview",
open: false,
links: [
{ text: "Fly Volumes Overview", path: "/docs/volumes/overview/" },
@@ -104,7 +113,7 @@
},
{
title: "Fly Kubernetes",
- path: "/docs/kubernetes/",
+ path: "/docs/kubernetes/fks-quickstart",
open: false,
links: [
{ text: "Fly Kubernetes Quickstart", path: "/docs/kubernetes/fks-quickstart/" },
@@ -126,9 +135,11 @@
{ text: "Private Networking", path: "/docs/networking/private-networking/" },
{ text: "Custom Private Networks", path: "/docs/networking/custom-private-networks/" },
{ text: "Flycast - Private Proxy Services", path: "/docs/networking/flycast/" },
+ { text: "Egress IP Addresses", path: "/docs/networking/egress-ips/" },
{ text: "Dynamic Request Routing", path: "/docs/networking/dynamic-request-routing/" },
{ text: "Custom Domains", path: "/docs/networking/custom-domain/" },
{ text: "Automate Certificates via API", path: "/docs/networking/custom-domain-api/" },
+ { text: "Understanding Cloudflare", path: "/docs/networking/understanding-cloudflare/" },
{ text: "Request Headers", path: "/docs/networking/request-headers/" },
{ text: "Run UDP Services", path: "/docs/networking/udp-and-tcp/" },
{ text: "TLS Support", path: "/docs/networking/tls/" }
@@ -146,6 +157,7 @@
path: "/docs/monitoring/logging-overview/",
links: [
{ text: "Live Tail Logs", path: "/docs/monitoring/live-tail-logs/" },
+ { text: "Logs API Options", path: "/docs/monitoring/logs-api-options/" },
{ text: "Search Logs", path: "/docs/monitoring/search-logs/" },
{ text: "Export Logs", path: "/docs/monitoring/exporting-logs/" },
{ text: "Error Codes", path: "/docs/monitoring/error-codes/" }
@@ -158,6 +170,7 @@
path: "/docs/security/",
open: false,
links: [
+ { text: "Organization Roles and Permissions", path: "/docs/security/org-roles-permissions/" },
{ text: "SSO for Organizations", path: "/docs/security/sso/" },
{ text: "Remove a Member from an Org", path: "/docs/security/remove-org-member/" },
{ text: "TLS Termination", path: "/docs/security/tls-termination/" },
@@ -177,14 +190,18 @@
{ text: "App Config Reference (fly.toml)", path: "/docs/reference/configuration/" },
{ text: "Architecture", path: "/docs/reference/architecture/" },
{ text: "Autoscaling", path: "/docs/reference/autoscaling/" },
+ { text: "AWS to Fly Overview", path: "/docs/reference/aws-to-fly-guide/" },
{ text: "Builders", path: "/docs/reference/builders/" },
+ { text: "Content Encoding", path: "/docs/reference/content-encoding/" },
{ text: "Fly Launch", path: "/docs/reference/fly-launch/" },
+ { text: "Health Checks", path: "/docs/reference/health-checks/" },
{ text: "Load Balancing", path: "/docs/reference/load-balancing/" },
{ text: "Machine Migration", path: "/docs/reference/machine-migration/" },
{ text: "Multiple Processes in Apps", path: "/docs/app-guides/multiple-processes/" },
{ text: "Fly Proxy", path: "/docs/reference/fly-proxy/" },
{ text: "Fly Proxy Autostop/Autostart", path: "/docs/reference/fly-proxy-autostop-autostart/" },
- { text: "Regions", path: "/docs/reference/regions/" }
+ { text: "Regions", path: "/docs/reference/regions/" },
+ { text: "Suspend/Resume", path: "/docs/reference/suspend-resume/" },
]
},
{
@@ -194,11 +211,14 @@
links: [
{ text: "Pricing", path: "/docs/about/pricing/" },
{ text: "Billing", path: "/docs/about/billing/" },
+ { text: "Cost Management", path: "/docs/about/cost-management/" },
+ { text: "Free Trial", path: "/docs/about/free-trial/" },
+ { text: "Support", path: "/docs/about/support/" },
{ text: "Engineering Jobs", path: "/docs/hiring/" },
{ text: "Healthcare on Fly.io", path: "/docs/about/healthcare/" },
- { text: "Support", path: "/docs/about/support/" },
{ text: "Extensions Program", path: "/docs/about/extensions/" },
{ text: "Extensions API", path: "/docs/reference/extensions_api/" },
+ { text: "Merch", path: "/docs/about/merch/" },
{ text: "Open Source", path: "/docs/about/open-source/" },
{ text: "Using Our Brand", path: "/docs/about/brand/" },
{ text: "Privacy Policy", path: "/legal/privacy-policy/" },
diff --git a/partials/_flyctl_nav.html.erb b/partials/_flyctl_nav.html.erb
index f22a6fb900..008ea39c66 100644
--- a/partials/_flyctl_nav.html.erb
+++ b/partials/_flyctl_nav.html.erb
@@ -31,10 +31,11 @@
{ text: "Manage IP addresses", path: "/docs/flyctl/ips/", type: "flyctl" },
{ text: "LiteFS Cloud", path: "/docs/flyctl/litefs-cloud/", type: "flyctl" },
{ text: "View App logs", path: "/docs/flyctl/logs/", type: "flyctl" },
+ { text: "Managed Postgres clusters", path: "/docs/flyctl/mpg/", type: "flyctl" },
{ text: "Manage Organizations", path: "/docs/flyctl/orgs/", type: "flyctl" },
{ text: "Ping your App", path: "/docs/flyctl/ping/", type: "flyctl" },
{ text: "Platform information", path: "/docs/flyctl/platform/", type: "flyctl" },
- { text: "Manage Postgres clusters", path: "/docs/flyctl/postgres/", type: "flyctl" },
+ { text: "Legacy Postgres clusters", path: "/docs/flyctl/postgres/", type: "flyctl" },
{ text: "Proxy connection to App", path: "/docs/flyctl/proxy/", type: "flyctl" },
{ text: "Manage Redis instances", path: "/docs/flyctl/redis/", type: "flyctl" },
{ text: "Manage App releases", path: "/docs/flyctl/releases/", type: "flyctl" },
@@ -45,6 +46,7 @@
{ text: "Move files to or from a VM", path: "/docs/flyctl/sftp/", type: "flyctl" },
{ text: "Manage SSH", path: "/docs/flyctl/ssh/", type: "flyctl" },
{ text: "View App status", path: "/docs/flyctl/status/", type: "flyctl" },
+ { text: "Manage Tigris storage", path: "/docs/flyctl/storage/", type: "flyctl" },
{ text: "Create a token", path: "/docs/flyctl/tokens/", type: "flyctl" },
{ text: "Check flyctl version", path: "/docs/flyctl/version/", type: "flyctl" },
{ text: "Manage Disks", path: "/docs/flyctl/volumes/", type: "flyctl" },
@@ -55,4 +57,4 @@
%>
<%= partial "/docs/partials/accordion_nav", locals: { nav: @nav } %>
-
\ No newline at end of file
+
diff --git a/partials/_guides_nav.html.erb b/partials/_guides_nav.html.erb
new file mode 100644
index 0000000000..8aad0bcccf
--- /dev/null
+++ b/partials/_guides_nav.html.erb
@@ -0,0 +1,74 @@
+<%= partial "/docs/partials/back_to_docs" %>
+
+<%
+ @nav = [
+ {
+ title: "Guides (Blueprints)",
+ path: "/docs/blueprints/",
+ accordion: false,
+ links: [
+ { text: "Guides Overview", path: "/docs/blueprints/" }
+ ]
+ },
+ {
+ title: "Architecture Patterns",
+ open: true,
+ links: [
+ { text: "Resilient apps use multiple Machines", path: "/docs/blueprints/resilient-apps-multiple-machines/" },
+ { text: "Getting Started with N-Tier Architecture", path: "/docs/blueprints/n-tier-architecture/" },
+ { text: "Shared Nothing Architecture", path: "/docs/blueprints/shared-nothing/" },
+ { text: "Session Affinity (a.k.a. Sticky Sessions)", path: "/docs/blueprints/sticky-sessions/" },
+ { text: "Multi-region databases and fly-replay", path: "/docs/blueprints/multi-region-fly-replay/" },
+ { text: "Deploying Remote MCP Servers", path: "/docs/blueprints/remote-mcp-servers/" }
+ ]
+ },
+ {
+ title: "Deployment & Developer Workflow",
+ open: true,
+ links: [
+ { text: "Seamless Deployments on Fly.io", path: "/docs/blueprints/seamless-deployments/" },
+ { text: "Rollback Guide", path: "/docs/blueprints/rollback-guide/" },
+ { text: "Git Branch Preview Environments on Github", path: "/docs/blueprints/review-apps-guide/" },
+ { text: "Staging and production isolation", path: "/docs/blueprints/staging-prod-isolation/" },
+ { text: "Per-User Dev Environments with Fly Machines", path: "/docs/blueprints/per-user-dev-environments/" },
+ { text: "Using base images for faster deployments", path: "/docs/blueprints/using-base-images-for-faster-deployments/" },
+ { text: "Managing Docker Images with Fly.io's Private Registry", path: "/docs/blueprints/using-the-fly-docker-registry/" }
+ ]
+ },
+ {
+ title: "Networking & Connectivity",
+ open: true,
+ links: [
+ { text: "Run private apps with Flycast", path: "/docs/blueprints/private-applications-flycast/" },
+ { text: "Jack into your private network with WireGuard", path: "/docs/blueprints/connect-private-network-wireguard/" },
+ { text: "Bridge your other deployments to Fly.io", path: "/docs/blueprints/bridge-deployments-wireguard/" },
+ { text: "Connecting to User Machines", path: "/docs/blueprints/connecting-to-user-machines/" },
+ { text: "Run an SSH server", path: "/docs/blueprints/opensshd/" }
+ ]
+ },
+ {
+ title: "Scaling, Performance & Observability",
+ open: true,
+ links: [
+ { text: "Observability for User Apps", path: "/docs/blueprints/observability-for-user-apps/" },
+ { text: "Autoscale Machines", path: "/docs/blueprints/autoscale-machines/" },
+ { text: "Autostart and autostop private apps", path: "/docs/blueprints/autostart-internal-apps/" },
+ { text: "Setting Hard and Soft Concurrency Limits", path: "/docs/blueprints/setting-concurrency-limits/" },
+ { text: "Using Fly Volume forks for faster startup times", path: "/docs/blueprints/volume-forking/" },
+ { text: "Going to Production with Healthcare Apps", path: "/docs/blueprints/going-to-production-with-healthcare-apps/" }
+ ]
+ },
+ {
+ title: "Background Jobs & Automation",
+ open: true,
+ links: [
+ { text: "Building Infrastructure Automation without Terraform", path: "/docs/blueprints/infra-automation-without-terraform/" },
+ { text: "Deferring long-running tasks to a distributed work queue", path: "/docs/blueprints/work-queues/" },
+ { text: "Task scheduling guide with Cron Manager and friends", path: "/docs/blueprints/task-scheduling/" },
+ { text: "Crontab with Supercronic", path: "/docs/blueprints/supercronic/" }
+ ]
+ }
+ ]
+%>
+
+<%= partial "/docs/partials/accordion_nav", locals: { nav: @nav } %>
diff --git a/partials/_litefs_nav.html.erb b/partials/_litefs_nav.html.erb
index ce4236de97..21047b2ab8 100644
--- a/partials/_litefs_nav.html.erb
+++ b/partials/_litefs_nav.html.erb
@@ -15,15 +15,6 @@
{ text: "FAQ", path: "/docs/litefs/faq" }
]
},
- {
- title: "LiteFS Cloud",
- accordion: true,
- links: [
- { text: "Using LiteFS Cloud for Backups", path: "/docs/litefs/cloud-backups" },
- { text: "Restoring from a LiteFS Cloud Backup", path: "/docs/litefs/cloud-restore" },
- { text: "Disaster Recovery from LiteFS Cloud", path: "/docs/litefs/disaster-recovery" }
- ]
- },
{
title: "Using LiteFS",
accordion: true,
diff --git a/partials/_machines_nav.html.erb b/partials/_machines_nav.html.erb
index bdb17e5654..02f3221aff 100644
--- a/partials/_machines_nav.html.erb
+++ b/partials/_machines_nav.html.erb
@@ -35,9 +35,13 @@
title: "Guides and Examples",
open: true,
links: [
+ { text: "Machine placement and regional capacity", path: "/docs/machines/guides-examples/machine-placement/" },
{ text: "Machine restart policy", path: "/docs/machines/guides-examples/machine-restart-policy/" },
{ text: "Machine sizing", path: "/docs/machines/guides-examples/machine-sizing/" },
- { text: "Run user code on Fly Machines", path: "/docs/machines/guides-examples/functions-with-machines/" }
+ { text: "Multi-container Machines", path: "/docs/machines/guides-examples/multi-container-machines/" },
+ { text: "Network policies", path: "/docs/machines/guides-examples/network-policies/" },
+ { text: "Run user code on Fly Machines", path: "/docs/machines/guides-examples/functions-with-machines/" },
+ { text: "One app per customer - why?", path: "/docs/machines/guides-examples/one-app-per-user-why/" }
]
},
{
diff --git a/partials/_mpg_nav.html.erb b/partials/_mpg_nav.html.erb
new file mode 100644
index 0000000000..a3b3ec6331
--- /dev/null
+++ b/partials/_mpg_nav.html.erb
@@ -0,0 +1,40 @@
+<%= partial "/docs/partials/back_to_docs" %>
+
+<%
+ @nav = [
+ {
+ title: "Managed Postgres",
+ path: "/docs/mpg/",
+ accordion: false,
+ links: [
+ { text: "Managed Postgres Overview", path: "/docs/mpg/" }
+ ]
+ },
+ {
+ title: "Getting Started",
+ open: true,
+ links: [
+ { text: "Create and Connect to a Managed Postgres Cluster", path: "/docs/mpg/create-and-connect/" },
+ { text: "Cluster Configuration Options", path: "/docs/mpg/configuration/" }
+ ]
+ },
+ {
+ title: "Guides and Examples",
+ open: true,
+ links: [
+ { text: "Phoenix with Managed Postgres", path: "/docs/mpg/guides-examples/phoenix-guide/" }
+ ]
+ },
+ {
+ title: "Management",
+ open: true,
+ links: [
+ { text: "Monitoring and Metrics", path: "/docs/mpg/metrics/" },
+ { text: "Import data from another postgres cluster", path: "/docs/mpg/import/" },
+ { text: "Supported Postgres Extensions", path: "/docs/mpg/extensions/" }
+ ]
+ }
+ ]
+%>
+
+<%= partial "/docs/partials/accordion_nav", locals: { nav: @nav } %>
diff --git a/partials/docs/_litefs_sunset.html.erb b/partials/docs/_litefs_sunset.html.erb
index 6508b22922..e485c59d00 100644
--- a/partials/docs/_litefs_sunset.html.erb
+++ b/partials/docs/_litefs_sunset.html.erb
@@ -1,3 +1,3 @@
-**LiteFS Cloud will be retired October 15, 2024.** [LiteFS](https://github.com/superfly/litefs) itself is not affected by the retirement of the LiteFS Cloud service. Learn more about how to disconnect from LiteFS Cloud and other options for disaster recovery in our community post: [Sunsetting LiteFS Cloud](https://community.fly.io/t/sunsetting-litefs-cloud/20829).
+**The documentation in this section is deprecated and refers to a product that was retired on October 15, 2024.** [LiteFS](https://github.com/superfly/litefs) itself is not affected by the retirement of the LiteFS Cloud service. Learn more about how to disconnect from LiteFS Cloud and other options for disaster recovery in our community post: [Sunsetting LiteFS Cloud](https://community.fly.io/t/sunsetting-litefs-cloud/20829).
\ No newline at end of file
diff --git a/postgres/connecting/connecting-external.html.markerb b/postgres/connecting/connecting-external.html.markerb
index c450f4cb6e..1857d2b805 100644
--- a/postgres/connecting/connecting-external.html.markerb
+++ b/postgres/connecting/connecting-external.html.markerb
@@ -5,6 +5,10 @@ layout: framework_docs
order: 2
---
+
+
+
+
Fly Postgres databases can be used by applications outside their Fly.io [internal private network](/docs/networking/private-networking/); this means in a different private network belonging to your organization, in another Fly organization, or outside Fly.io altogether.
We don't expose Postgres apps to the internet by default. To get this working, you'll need to make two adaptations: configuring your Postgres app to accept connections from the Fly proxy, and providing a publicly-resolvable hostname to your app.
diff --git a/postgres/getting-started/what-you-should-know.html.md b/postgres/getting-started/what-you-should-know.html.md
index 810e044598..086e727e40 100644
--- a/postgres/getting-started/what-you-should-know.html.md
+++ b/postgres/getting-started/what-you-should-know.html.md
@@ -1,6 +1,5 @@
---
title: "This Is Not Managed Postgres"
-objective: Read our warnings about why we don't call Fly Postgres "managed"!
layout: framework_docs
order: 1
---
@@ -34,17 +33,19 @@ Deploying a Fly Postgres database means you may need to manage the following:
There are a lot of knobs to turn, but `fly pg config` only supports a few of them out of the box. For more details, see [Postgres Configuration Tuning](https://fly.io/docs/postgres/managing/configuration-tuning/).
- **Advanced customization** - TimescaleDB is included in the default image and can be enabled with these [instructions](https://fly.io/docs/postgres/managing/enabling-timescale/). If your application demands additional Postgres extensions or something else in the VM, you can [fork and maintain your own branch of Fly's open source Postgres Flex app](https://github.com/fly-apps/postgres-flex).
-### Recommended External Providers
+## Recommended Managed Postgres option
-If you want a fully managed database solution for your Fly Apps, there are many great options, including:
+- [Fly.io's Managed Postgres](/docs/mpg/) We recommend Fly.io Managed Postgres, a production-ready Postgres service that handles the hard parts for you: high availability, automatic failover, encrypted backups, monitoring and metrics, seamless scaling, and 24/7 support to keep your data fast, safe, and always online.
+
+## Other Managed Postgres providers
+
+Other providers:
- [Crunchy Bridge Managed Postgres](https://www.crunchydata.com/products/crunchy-bridge+external) (on AWS, Azure, GCP, or Heroku)
- [Neon Serverless Postgres](https://neon.tech/+external)
- [PlanetScale Serverless MySQL](https://planetscale.com/+external) ([guide to use with Fly Apps](https://fly.io/docs/app-guides/planetscale/))
- [Supabase Postgres](https://supabase.com/)
-### Other Places
-
You can connect your Fly Apps to the usual suspects, too:
- [Amazon RDS for PostgreSQL](https://aws.amazon.com/rds/postgresql/+external)
diff --git a/postgres/index.html.md b/postgres/index.html.md
index b934c2cd1b..a1722a2416 100644
--- a/postgres/index.html.md
+++ b/postgres/index.html.md
@@ -1,17 +1,17 @@
---
-title: "Fly Postgres"
+title: "Fly Postgres (Unmanaged)"
layout: framework_docs
redirect_from: /docs/reference/postgres/
toc: false
---
-
-
-**Important:** We are not able to provide support or guidance for this product. Use with caution as this is not [managed postgres](https://fly.io/docs/postgres/getting-started/what-you-should-know/).
+
+
+
[Postgres](https://www.postgresql.org/+external), formally known as PostgreSQL, is a powerful open source object relational database system that's used by many popular web frameworks to persist application data.
-Fly Postgres is a Fly app with flyctl sugar on top to help you bootstrap and manage a database cluster for your apps. It comes with most commonly used functionality (replication, failover, metrics, monitoring and daily snapshots).
+Fly Postgres (unmanaged) is a Fly app with flyctl sugar on top to help you bootstrap and manage a database cluster for your apps. It comes with most commonly used functionality (replication, failover, metrics, monitoring and daily snapshots).
**[Read about why Fly Postgres is not the same thing as a managed database service](/docs/postgres/getting-started/what-you-should-know).**
diff --git a/postgres/managing/backup-and-restore.html.markerb b/postgres/managing/backup-and-restore.html.markerb
index d139bfa4ff..8247f9a79e 100644
--- a/postgres/managing/backup-and-restore.html.markerb
+++ b/postgres/managing/backup-and-restore.html.markerb
@@ -64,8 +64,8 @@ fly volumes list -a
```
```output
ID NAME SIZE REGION ATTACHED VM CREATED AT
-vol_x915grn008vn70qy pg_data 10GB atl b780ce3d 2 weeks ago
-vol_ke628r677pvwmnpy pg_data 10GB atl 359d0e24 2 weeks ago
+vol_x915grn008vn70qy pg_data 10GB sjc b780ce3d 2 weeks ago
+vol_ke628r677pvwmnpy pg_data 10GB sjc 359d0e24 2 weeks ago
```
The number of volumes varies depending on how many database replicas were elected when provisioning the database. One primary database and one replica yields 2 volumes.
diff --git a/rails/advanced-guides/sqlite3.html.md b/rails/advanced-guides/sqlite3.html.md
index bf3315327b..a7290751f5 100644
--- a/rails/advanced-guides/sqlite3.html.md
+++ b/rails/advanced-guides/sqlite3.html.md
@@ -8,7 +8,7 @@ status: beta
While Rails applications on [Fly.io](https://fly.io) normally run on Postgres databases, you can
choose to run them on [sqlite3](https://www.sqlite.org/index.html).
-To make this work, you will need to place your databases on persistent [Volumes](https://www.sqlite.org/index.html)
+To make this work, you will need to place your databases on persistent [Volumes](/docs/volumes/overview/)
as your deployment image will get overwritten the next time you deploy.
Volumes are limited to one host, this currently means that fly.io hosted Rails applications that use
diff --git a/rails/getting-started/migrate-from-heroku.html.md b/rails/getting-started/migrate-from-heroku.html.md
index 5eebda2f91..d91b7e899b 100644
--- a/rails/getting-started/migrate-from-heroku.html.md
+++ b/rails/getting-started/migrate-from-heroku.html.md
@@ -4,6 +4,10 @@ layout: framework_docs
order: 2
---
+
+
+
+
This guide runs you through how to migrate a basic Rails application off of Heroku and onto Fly. It assumes you're running the following services on Heroku:
* Puma web server
diff --git a/reference/autoscaling.html.md b/reference/autoscaling.html.md
index 852888de0a..fa5d7f6f9c 100644
--- a/reference/autoscaling.html.md
+++ b/reference/autoscaling.html.md
@@ -4,6 +4,10 @@ layout: docs
nav: firecracker
---
+
+
+
+
Autoscaling adjusts the number of running or created Fly Machines dynamically. We support two forms of autoscaling:
- Autostop/autostart Machines
diff --git a/reference/aws-to-fly-guide.html.md b/reference/aws-to-fly-guide.html.md
new file mode 100644
index 0000000000..a076b0dc60
--- /dev/null
+++ b/reference/aws-to-fly-guide.html.md
@@ -0,0 +1,71 @@
+---
+title: Migrating from AWS to Fly.io Overview
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-06-09
+---
+
+
+
+
+
+
+**Fly.io runs apps close to your users, by giving you fast-starting VMs ("Fly Machines") in regions worldwide.** This guide is for folks moving apps from AWS. It walks through the major architectural differences you'll hit and how to adjust your deployment model.
+
+
+
+Migrating from AWS to Fly.io means rethinking a few assumptions. The two platforms run apps differently, wire networks differently, and give you different tools to scale and persist data. This is a whirlwind tour of what changes when you leave the AWS cloud mothership.
+
+### Compute
+
+First up: compute. Fly.io runs apps as virtual machines, but it doesn't use AMIs or EC2. Instead, you give us a Docker image, we unpack it, and that becomes the root filesystem for a VM we spin up for you. We call these [Fly Machines](/docs/machines/overview/). They're fast. And they're small. And they boot fast because there's no hypervisor cold-start time and no shared kernel games like you'd find in ECS or EKS.
+
+But here's the catch: unless you mount a volume, the root filesystem is ephemeral. It disappears on restart. Any data written there is toast unless you store it somewhere else. If your app currently depends on an EBS volume or even just writes temp files it expects to survive a restart, you'll need to rethink that.
+
+### Storage
+
+[Fly Volumes](/docs/volumes/overview/) are our answer to persistent storage. They're slices of NVMe mounted directly on the physical host. They're not network-attached. This means they're fast and simple, but they're tied to a specific host. If your machine dies, you can boot another and reattach the volume. Think EBS, minus the network indirection and regional abstraction. You can also store large static assets like ML models in volumes, which keeps your Docker images lean and your deploys snappy.
+
+For object storage, we offer [Tigris](/docs/tigris/). It's S3-compatible and runs on Fly.io itself. If you're migrating from AWS S3, there's a handy [shadow bucket](/docs/tigris/#migrating-to-tigris-with-shadow-buckets) mode: Tigris can fall back to fetching from your existing S3 bucket if a file isn't yet copied over. First request pulls from S3; future requests hit local. Writes are synced, too, which makes a gradual migration painless.
+
+### Networking
+
+Talking to AWS-hosted services, like RDS, takes a bit of finessing. There's no direct WireGuard bridge into your AWS VPC. If you want to connect to RDS securely, the simplest approach is to run PgBouncer as a Fly Machine with a static egress IP. Allowlist that IP in your RDS security group. Then your app machines connect to PgBouncer over Fly's private network, and it forwards to RDS.
+
+Speaking of [networks](/docs/networking/): Fly.io apps live on an isolated private network per org, connected over WireGuard. Machines talk to each other via `.internal` hostnames (direct) or `.flycast` (load-balanced via our proxy). This setup is pretty close to private subnets and security groups in AWS. You usually only expose your edge-facing apps to the public internet.
+
+Instead of regional load balancers, we use [Anycast](/docs/networking/services/#anycast-ip-addresses). Your DNS points to one global IP. The Fly Proxy routes requests to the nearest edge server, which forwards traffic to a healthy app instance based on load and latency. That means no more manually balancing between `us-east-1` and `us-west-2`.
+
+### Scaling
+
+Scaling works differently too. Machines are not created on-demand based on request traffic. You provision them up front, and we start/stop them as needed. That means your max spend is predictable. Want [autoscaling](/docs/reference/autoscaling/)? Monitor metrics via Prometheus, and scale using flyctl or an API client. You own the automation.
+
+### Deployments and Secrets
+
+Which brings us to Infrastructure-as-code (IaC). Most users glue things together with Bash scripts and `flyctl`, and manage state by convention. It's low-friction but low-abstraction. And it’s still less of a headache than CloudFormation. More infrastructure automation ideas are [available here](/docs/blueprints/infra-automation-without-terraform/).
+
+On the plus side, deployments can be [zero-downtime](/docs/blueprints/seamless-deployments/) if you use [health checks](/docs/reference/health-checks/) and run multiple machines. Define those health checks in `fly.toml`, and the proxy will route around unhealthy nodes. You can even run DB migrations with a `release_command` before a deploy, and roll back by pushing a previous image tag.
+
+Secrets? Use [Fly Secrets](/docs/apps/secrets/). They get mounted as env vars at runtime and stay encrypted at rest. Similar to AWS Secrets Manager or Parameter Store, but simpler. We also have a new Secrets API that works like AWS KMS to allow apps to encrypt/decrypt data with centrally-managed keys.
+
+### Databases
+
+Databases? We offer [Managed Postgres](/docs/mpg/), our fully-managed database service that handles all aspects of running production PostgreSQL databases, similar to RDS. We take care of automatic backups/recovery, high availability with automatic failover, performance monitoring and metrics, resource scaling, 24/7 support, and automatic data encryption.
+
+Other database options include managed Redis from our partner Upstash, running your own Redis or Valkey, distributed SQLite via LiteFS, or vector DBs like LanceDB with Tigris.
+
+### Monitoring
+
+For [monitoring](/docs/monitoring/metrics/), you get Prometheus metrics and Grafana dashboards. There's no native alerting yet, but many folks run their own Prometheus with Alertmanager, or use Grafana to set up alerts. This involves more user setup compared to AWS CloudWatch alarms, but you gain a wealth of resources from the Grafana and Prometheus communities.
+
+### In a nutshell
+
+AWS is a sprawling platform with deep abstractions. Fly.io strips a lot of that away. You get rawer access to your infra and better latency for your users, but you might trade some convenience. Migration is generally less about translating concepts and more about rethinking how your app is built and deployed.
+
+### Related Reading
+
+- [Fly Apps](/docs/apps/overview/)
+- [Fly Volumes](/docs/volumes/overview/)
+- [Managed Postgres](/docs/mpg/)
+- [Going to Production Checklist](/docs/apps/going-to-production/)
\ No newline at end of file
diff --git a/reference/configuration.html.markerb b/reference/configuration.html.markerb
index 10c957cbf0..07e16d582d 100644
--- a/reference/configuration.html.markerb
+++ b/reference/configuration.html.markerb
@@ -52,7 +52,9 @@ The following options are available to control the lifecycle of a running applic
### `kill_signal` option
-When shutting down a Fly Machine, by default, Fly.io sends a `SIGINT` signal to the running process. Typically this triggers a hard shutdown option on most applications. The `kill_signal` option allows you to change what signal is sent so that you can trigger a softer, less disruptive shutdown. Options are `SIGINT` (default), `SIGTERM`, `SIGQUIT`, `SIGUSR1`, `SIGUSR2`, `SIGKILL`, or `SIGSTOP`. For example, to set the kill signal to SIGTERM, you would add:
+When shutting down a Fly Machine, Fly.io sends a `SIGINT` signal to the running process by default. Typically this triggers a hard shutdown option on most applications. The `kill_signal` option lets you override that with a different signal so that you can trigger a softer, less disruptive shutdown: `SIGTERM`, `SIGQUIT`, `SIGUSR1`, `SIGUSR2`, `SIGKILL`, or `SIGSTOP`.
+
+For example, to set the kill signal to SIGTERM, you would add:
```toml
kill_signal = "SIGTERM"
@@ -60,7 +62,9 @@ kill_signal = "SIGTERM"
### `kill_timeout` option
-The time to wait, in seconds, before stopping a Machine after sending the `SIGINT` signal or the signal set by `kill_signal`. Set `kill_timeout` to a value that gives your app enough time to exit gracefully. The default setting is 5 seconds. The maximum `kill_timeout` is 300 seconds (five minutes).
+Note: `kill_timeout` settings should be considered best-effort, and your app needs to be prepared to handle shorter stopping times
+
+This sets how long Fly.io waits (in seconds) after sending the `kill_signal` before moving on to a forced shutdown. Set `kill_timeout` to a value that gives your app enough time to exit gracefully. The default is 5 seconds. You can set it up to a maximum of 300 seconds (5 minutes).
For example, to set the timeout to two minutes:
@@ -68,6 +72,28 @@ For example, to set the timeout to two minutes:
kill_timeout = 120
```
+### How the shutdown sequence works
+
+These options come into play during controlled shutdowns such as:
+
+* When using `fly deploy`
+* When using `fly machine stop`
+* When a machine stops due to `auto_stop_machines`
+
+The sequence looks like this:
+
+1. Fly sends the `kill_signal` to the main process (defined by your Dockerfile’s `ENTRYPOINT` or `CMD`, or overridden via `--entrypoint`).
+
+1. It waits for up to `kill_timeout` seconds.
+
+1. Then it sends `SIGTERM` unconditionally.
+
+This gives your app a chance to shut down cleanly: finish requests, close connections, stop taking new work. It's designed for apps that can wrap up in under five minutes.
+
+Many workers will re-queue unfinished jobs so another machine can pick them up. If your app doesn't support that, or needs longer to shut down, you'll want to plan around that.
+
+You can watch shutdown behavior by watching `fly logs` during a deploy or idle stop.
+
## Console command
The command to run when you run the [`fly console` command](/docs/flyctl/console/). Configure the `console_command` field with the command that opens your framework's console, and then `fly console` will run that command automatically in a new, dedicated Machine. The new Machine is configured with the image and environment from your app’s latest release, but your app isn’t started, and no traffic will be routed to it. The Machine gets destroyed when you exit the console.
@@ -172,19 +198,24 @@ This section configures deployment-related settings such as the release command
### Run one-off commands before releasing a deployment
-To run a command in a temporary Machine—using the app's successfully built Docker image—*before* the release is deployed, define a `release_command`:
+The `release_command` lets you run a one-off task, like a database migration, before any of your deployed Machines are created or updated. It runs in a temporary Machine using the newly built image, once per deploy attempt. This is a common pattern for frameworks like Rails or Phoenix, but it's not limited to them. You can use it for any setup task that needs to run once, not on every app boot.
+
+If the command fails, the deploy will stop. The goal is to make deployment safer: do the risky part first, and only continue if it succeeds.
+
+To run a command in a temporary Machine *before* the release is deployed, define a `release_command`:
```toml
[deploy]
release_command = "bin/rails db:prepare"
```
-The `release_command` value replaces `CMD` in the temporary Machine. This is useful for running database migrations before app Machines are created or updated with the new release. Note that the Docker image's `ENTRYPOINT` is not overridden by the `release_command`; `ENTRYPOINT` always runs.
+The `release_command` value replaces `CMD` in the temporary Machine. This is most commonly used for database migrations, but can be any task that prepares your app for the new release. Note that the Docker image's `ENTRYPOINT` is not overridden by the `release_command`; `ENTRYPOINT` always runs.
-By default, the temporary Machine has full access to the network, environment variables and secrets, but *not* to persistent volumes. Changes made to the file system on the temporary Machine will not be retained or deployed. The building/compiling of your project should be done in your Dockerfile. If you need to modify persistent volumes or configure your application, consider making use of `CMD` or `ENTRYPOINT` in your Dockerfile, or of a [process group command](#the-processes-section).
+By default, the temporary Machine has full access to the network, environment variables and secrets, but *not* to persistent volumes. Changes made to the file system on the temporary Machine will not persist. It has no volumes attached, and the Machine is destroyed after the command completes. The building/compiling of your project should be done in your Dockerfile. If you need to modify persistent volumes or configure your application, consider making use of `CMD` or `ENTRYPOINT` in your Dockerfile, or of a [process group command](#the-processes-section).
The temporary Machine inherits the size from the largest Machine in the default process group of the app as of flyctl v0.0.508 (they also default larger on empty/new apps, using the Machine `shared-cpu-2x` preset). The default process group is defined as either the `app` process, or the first process group in alphabetical order (if an `app` process is not present).
-It is also possible to explicitly define the Machine size by setting `[deploy.release_command_vm]` like in:
+
+It is also possible to explicitly define the Machine size by setting `[deploy.release_command_vm]`, for example:
```toml
[deploy]
@@ -203,6 +234,18 @@ The environment variable `RELEASE_COMMAND=1` is set within the temporary release
By default, the release command has 5 minutes to complete before it is killed. It is possible to increase or decrease the timeout with `fly deploy --release-command-timeout=10m` or by setting `release_command_timeout = "10m"` in `[deploy]` section of `fly.toml`.
+For more context, see our [Seamless Deployments guide](/docs/blueprints/seamless-deployments/#one-off-tasks-with-release_command).
+
+
+**Things to know about `release_command`:**
+- Replaces `CMD`, but not `ENTRYPOINT`
+- Runs in a temporary Machine with no volumes
+- Inherits environment, secrets, and network config
+- Fails the deploy on a non-zero exit code
+- Times out after 5 minutes by default (configurable)
+- Logs are streamed to `fly deploy` and `fly logs`
+
+
### Picking a deployment strategy
```toml
@@ -244,7 +287,7 @@ Fractional values, between zero and one, provide a percentage of Machines that c
### Dealing with timeout errors while deploying a new version
-Every time `fly deploy` runs, it builds a new docker image, pushes it to an internal registry, and then updates or creates Machines for the app. Sometimes the image is too large, or the platform is slower than usual pulling the new image layers, triggering the default 5 minute timeout waiting for the Machine to be in a started state.
+Every time `fly deploy` runs, it builds a new Docker image, pushes it to an internal registry, and then updates or creates Machines for the app. Sometimes the image is too large, or the platform is slower than usual pulling the new image layers, triggering the default 5 minute timeout waiting for the Machine to be in a started state.
The good thing is that this timeout can be controlled in two ways, with the `--wait-timeout` option, for example `fly deploy --wait-timeout=10m`, or as a more permanent `fly.toml` setting:
@@ -367,6 +410,49 @@ In the following example, `h2_backend` is set to `true`, which allows the app to
h2_backend = true
```
+### `http_service.http_options.replay_cache`
+
+Configures session-based replay caching. When your app issues a `fly-replay` response, Fly Proxy can cache that replay decision based on cookie or header values, automatically applying it to subsequent requests with the same identifier.
+
+Each `http_options.replay_cache` entry configures a caching rule:
+
+```toml
+[[http_service.http_options.replay_cache]]
+ path_prefix = "/"
+ ttl_seconds = 300
+ type = "cookie"
+ name = "session_id"
+ allow_bypass = false
+```
+
+For documentation on replays, and further explanation on caching, see [Session-based Replay Caching](/docs/networking/dynamic-request-routing/#session-based-replay-caching).
+
+#### `http_service.http_options.replay_cache.path_prefix`
+
+Path pattern where this caching rule applies.
+
+This can be a path like `/api` (applies to any hostname) or include a hostname like `example.com/api`. When multiple rules match a request, the longest matching path takes precedence.
+
+If including a hostname, do not include the protocol or the port. If no hostname is provided, the cache rule applies to all hostnames, but each hostname retains a separate cache.
+
+Implicitly ends with `/*`, meaning that `example.com/api`, `example.com/api/`, and `example.com/api/*` are all equivalent.
+
+#### `http_service.http_options.replay_cache.ttl_seconds`
+
+How many seconds to cache the replay decision. Minimum value is 10 seconds.
+
+#### `http_service.http_options.replay_cache.type`
+
+Either `"cookie"` or `"header"`. Specifies whether to key the cache on a cookie or request header.
+
+#### `http_service.http_options.replay_cache.name`
+
+Name of the cookie or header to use as the cache key.
+
+#### `http_service.http_options.replay_cache.allow_bypass`
+
+Optional boolean. When `true`, allows clients to bypass the cache by setting the `fly-replay-cache-control: skip` request header. Default is `false`.
+
### `http_service.tls_options`
Configure the TLS versions and ALPN protocols that Fly's edge will use to terminate TLS for your application with:
@@ -402,7 +488,7 @@ Roughly translated, this section says every thirty seconds, perform an HTTP GET
Times are in milliseconds unless units are specified.
* `grace_period`: The time to wait after a Machine starts before checking its health. Make sure this is long enough for your app to start up. For example, if your app takes 2 seconds to start up, give it some runway by setting `grace_period` to at least 3 seconds.
-* `interval`: The time between connectivity checks. There should be a balance between the interval and the grace period. If it's long and your grace_period shorter than your app's startup time, health check will take too long adding you your deployment time.
+* `interval`: The time between connectivity checks. There should be a balance between the interval and the grace period. If interval is long and grace_period is shorter than your app's startup time, the health check will take too long, adding to your deployment time.
* `timeout`: The maximum time a connection can take before being reported as failing its health check.
* `method`: The HTTP method to be used for the check. If omitted, the default is `get`.
* `path`: The path of the URL to be requested.
@@ -415,7 +501,7 @@ Times are in milliseconds unless units are specified.
### The `http_service.machine_checks` section
-You can use the same `machine_checks` section for HTTP services as for [`services`](#services-machine-checks). The `machine_checks` section defines parameters for checking the health of a service.
+You can use the same `machine_checks` section for HTTP services as for [`services`](#services-machine_checks). The `machine_checks` section defines parameters for checking the health of a service.
```toml
[[http_service.machine_checks]]
@@ -559,6 +645,49 @@ In the following example, `h2_backend` is set to `true`, which allows the app to
h2_backend = true
```
+### `services.ports.http_options.replay_cache`
+
+Configures session-based replay caching. When your app issues a `fly-replay` response, Fly Proxy can cache that replay decision based on cookie or header values, automatically applying it to subsequent requests with the same identifier.
+
+Each `http_options.replay_cache` entry configures a caching rule:
+
+```toml
+ [[services.ports.http_options.replay_cache]]
+ path_prefix = "/"
+ ttl_seconds = 300
+ type = "cookie"
+ name = "session_id"
+ allow_bypass = false
+```
+
+For documentation on replays, and further explanation on caching, see [Session-based Replay Caching](/docs/networking/dynamic-request-routing/#session-based-replay-caching).
+
+#### `services.ports.http_options.replay_cache.path_prefix`
+
+Path pattern where this caching rule applies.
+
+This can be a path like `/api` (applies to any hostname) or include a hostname like `example.com/api`. When multiple rules match a request, the longest matching path takes precedence.
+
+If including a hostname, do not include the protocol or the port. If no hostname is provided, the cache rule applies to all hostnames, but each hostname retains a separate cache.
+
+Implicitly ends with `/*`, meaning that `example.com/api`, `example.com/api/`, and `example.com/api/*` are all equivalent.
+
+#### `services.ports.http_options.replay_cache.ttl_seconds`
+
+How many seconds to cache the replay decision. Minimum value is 10 seconds.
+
+#### `services.ports.http_options.replay_cache.type`
+
+Either `"cookie"` or `"header"`. Specifies whether to key the cache on a cookie or request header.
+
+#### `services.ports.http_options.replay_cache.name`
+
+Name of the cookie or header to use as the cache key.
+
+#### `services.ports.http_options.replay_cache.allow_bypass`
+
+Optional boolean. When `true`, allows clients to bypass the cache by setting the `fly-replay-cache-control: skip` request header. Default is `false`.
+
#### `services.ports.proxy_proto_options`
Configure the version of the PROXY protocol that your app accepts. Version 1 is the default.
@@ -631,7 +760,6 @@ When a service is running, Fly Proxy can check up on it by connecting to a port.
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
- restart_limit = 0
timeout = "2s"
```
Times are in milliseconds unless units are specified.
@@ -693,7 +821,9 @@ This example uses the `curl` image to run a test. It checks that the Machine is
* `kill_signal`: The signal to send to the test process if it runs too long. Defaults to the signal of the image, if a custom image is set.
* `kill_timeout`: The time to wait before sending the kill signal. Defaults to the timeout of the image, if a custom image is set.
-Machine checks are especially useful for canary deploys. `flyctl` will spawn a new Machine, ensure all machine capabilities are functional, and then deploy the rest of the Machines in your app.
+Machine checks are especially useful for `canary` deploys. `flyctl` will spawn a new Machine, ensure all machine capabilities are functional, and then deploy the rest of the Machines in your app.
+
+Machine checks are supported for apps using the `rolling` and `canary` deployment strategies.
## The `mounts` section
@@ -742,6 +872,20 @@ For example, the following config retains snapshots of volumes for 14 days:
snapshot_retention = 14
```
+### `scheduled_snapshots`
+
+Optional. Enable or disable automatic daily snapshots for a volume.
+
+The default is true (enabled) when unset.
+
+For example, the following config disables automatic daily snapshots:
+```toml
+[mounts]
+ source = "myapp_cache"
+ destination = "/cache"
+ scheduled_snapshots = false
+```
+
### `auto_extend_size_threshold`
Optional. The threshold of storage used on a volume, by percentage, that triggers extending the volume's size by the value of `auto_extend_size_increment`,
@@ -749,17 +893,21 @@ unless the resulting size would exceed `auto_extend_size_limit`.
### `auto_extend_size_increment`
-The increment, in GB, by which to extend the volume after reaching the `auto_extend_size_threshold`. Required with `auto_extend_size_threshold`.
+The increment, in GB, by which to extend the volume after reaching the `auto_extend_size_threshold`.
### `auto_extend_size_limit`
-Maximum size, in GB, of a volume after an extension is completed. Required with `auto_extend_size_threshold`.
+Maximum size, in GB, of a volume after an extension is completed.
+
+
+When using the auto-extension feature for volumes, both `auto_extend_size_increment` and `auto_extend_size_limit` are required. Without these values, the volume won't resize automatically, even if the usage threshold is reached.
+
### Auto extend volume size configuration
You can optionally configure volumes to automatically extend when they reach a usage threshold.
-For example, the following config extends the size of the volume by 1GB when it reaches 80% capacity. The volume extensions are limited to 5GB in total. `auto_extend_size_limit` is optional.
+For example, the following config extends the size of the volume by 1GB when it reaches 80% capacity. The volume extensions are limited to 5GB in total.
```toml
[[mounts]]
@@ -778,18 +926,23 @@ The default Machine size is `shared-cpu-1x` but it is not enforced if not specif
If you care about a predictable _scaling up from zero_ case, or if you have different compute requirements for different process groups, then it is highly recommended to add this section to `fly.toml`. Once compute requirements are set, `fly deploy` and `fly scale count` commands will enforce them. If you change the size of the Machines using `fly scale vm` (or its sibling `fly scale memory`) then it will be reset on next `fly deploy` unless `fly.toml` is updated accordingly. Learn more about [Machine size configuration precedence](/docs/apps/scale-machine/#machine-size-configuration-precedence).
+
+If you update your Machine size using `fly scale vm` or `fly scale memory` but you still have a `[[vm]]` section in your `fly.toml` file, the next `fly deploy` will reset your Machines to the configuration in the file. If you want to manually scale your Machines and keep those settings, remove the `[[vm]]` section from your `fly.toml` file.
+
+
All keys are optional and `size` has lower precedence than all other keys.
```toml
[[vm]]
size = "shared-cpu-2x"
- memory = "2gb"
- cpus = 4
- cpu_kind = "performance"
+ memory = "1gb"
+ cpus = 2
+ cpu_kind = "shared"
gpus = 1
gpu_kind = "a100-pcie-40gb"
kernel_args = "no-hlt=true"
host_dedication_id = "customer-id"
+ persist_rootfs = "never"
processes = ["app"]
```
@@ -831,6 +984,14 @@ Additional kernel parameters provided to the kernel when booting the VM.
In the case of having a dedicated host, this is the value to set so Machines are launched on dedicated hardware.
+### persist_rootfs
+
+The persistence of the root filesystem across restarts and updates. Valid values are `never` (default), `restart`, and `always`.
+
+- `never`: The root filesystem is ephemeral and will not be persisted across restarts or updates.
+- `restart`: The root filesystem is persisted across restarts, but not across updates.
+- `always`: The root filesystem is persisted across restarts and updates.
+
### `processes`
Optionally, you can specify a list of [processes](#the-processes-section) to limit by process group. You can even specify two different `[[vm]]` sections to have different compute requirements per process group. Note the need to use double brackets if there are multiple `[[vm]]` sections defined.
@@ -860,7 +1021,7 @@ processes = ["worker"]
processes = ["app"]
```
-When a machine unexpectedly exits, our scheduling machinery follows a machine’s restart policy to restart it without your intervention. These policies are quite similar to docker’s container restart policies:
+When a machine unexpectedly exits, our scheduling machinery follows a machine’s restart policy to restart it without your intervention. These policies are quite similar to Docker’s container restart policies:
- always: we’ll attempt to restart the machine no matter the exit code.
- never: we won’t restart the machine even if it exits with a non-zero exit code.
@@ -976,18 +1137,32 @@ Check out [Metrics on Fly.io](/docs/reference/metrics/) for more information abo
## The `statics` sections
-When `statics` are set, requests under `url_prefix` that are present as files in `guest_path` will be delivered directly to clients, bypassing your web server. These assets are extracted from your Docker image and delivered directly from our proxy on worker hosts.
+You can use the `[[statics]]` section in your `fly.toml` to serve static files such as images, CSS, or JavaScript for your application. There are two ways to serve statics:
+- From a running Fly Machine
+- From a Tigris object storage bucket.
+
+### Serving statics from a Fly Machine
+
+When you add a `[[statics]]` section to `fly.toml`, the Fly Proxy intercepts requests matching your `url_prefix`. If the requested file exists in your `guest_path`, the request is routed to a static file server running inside your machine. This bypasses your main web server process. Your machine must be running for static files to be served.
+
+If your machine is stopped, static files will not be served. If `autostart` is enabled, the machine will be started during statics requests as well as during normal requests to the app. Static assets are always served directly from your VM, not from the Fly edge or proxy host.
```toml
[[statics]]
guest_path = "/app/public"
url_prefix = "/public"
```
-Each `[[statics]]` block maps a URL prefix to a path inside your container. You can have up to 10 mappings in an application.
-The "guest path" --- the path inside your container where the files to serve are located --- can overlap with other static mappings; the URL prefix should not (so, two mappings to `/public/foo` and `/public/bar` are fine, but two mappings to `/public` are not).
+In this example, a request to `/public/image.png` is served from `/app/public/image.png` inside your running machine. You can define up to 10 `[[statics]]` mappings per application.
+
+The `guest_path` is the directory in your Docker image to serve files from. It may overlap across mappings, but `url_prefix` values must not overlap. Only one static mapping is honored for a given prefix. For example, two mappings to `/public/foo` and `/public/bar` are allowed, but two mappings to `/public` are not.
+
+### Serving statics from Tigris object storage
+
+You can serve static files directly from a Tigris object storage bucket. This lets you host assets or full static sites from a bucket instead of baking them into your Docker image.
-Alternatively, you can serve statics directly from object storage using [Tigris](/docs/tigris/). Similar to machine statics, requests under `url_prefix` are served from static assets in your Tigris bucket under `guest_path`. The same rules apply for overlapping URL prefixes.
+Static files in Tigris buckets are always delivered through your application’s running VM, not directly from the Fly edge. If no machine is running, requests for Tigris statics will not be served.
+However, if `autostart` is enabled for your app, incoming requests for static assets will automatically start a machine as needed, the same as for normal application traffic.
```toml
[[statics]]
@@ -996,7 +1171,9 @@ Alternatively, you can serve statics directly from object storage using [Tigris]
tigris_bucket = "my-bucket"
```
-Serving statics from Tigris supports index documents. When an index document is specified, requests to the root or any of its subfolders serve the index document.
+In this configuration, requests under `/public` are served from files stored in `my-bucket` under the `/app/public` path. Your machine must be running for these files to be served. If your machine scales to zero, static assets will not be accessible.
+
+When handling trailing slashes, requests to a directory path such as `/public/dir/` look for an `index.html` file, if you specify one. Requests without a trailing slash such as `/public/dir` may not serve as expected. Consider using redirects or documenting this for your users. Tigris statics support the `index_document` key, which allows you to serve a default file for a directory path.
```toml
[[statics]]
@@ -1006,27 +1183,33 @@ Serving statics from Tigris supports index documents. When an index document is
index_document = "index.html"
```
-In this configuration, a request to `https://example.com/public/` will serve the file `/app/public/index.html` stored in `my-bucket`. Similarly, a request to `https://example.com/public/assets/`, will serve the file `/app/public/assets/index.html`.
-
-### Caveats
+In this configuration, a request to `https://example.com/public/` serves `/app/public/index.html` from the bucket. Similarly, `https://example.com/public/assets/` serves `/app/public/assets/index.html`.
-This feature should not be compared directly with a CDN, for the following reasons:
+Tigris statics are billed at Tigris pricing. Data egress from Tigris may incur both Tigris and Fly Proxy egress charges.
-* This feature does not exempt you from having to run a web service in your Machine.
+Do not overlap `url_prefix` values; only one static mapping will be honored for a given prefix.
-* Serving an index document is only supported for statics served directly from Tigris. The index document must have the same name in all the relevant locations in your Tigris bucket.
-* For statics served from your container, it will not find `index.html` at the root. The full path must be requested.
+### Caveats
-* You can't set `Cache-Control` or any other headers on assets. If you need those, you'll need to deliver them from your application and set the relevant headers.
+This feature should not be compared directly with a CDN, for the following reasons:
-* Assets are not delivered, by default, from all edge Fly.io regions. Rather, assets are delivered from the regions the application is deployed in.
+- This feature does not exempt you from having to run a web service in your Machine.
+- You must have a running machine for statics to be served. If your machine is stopped, static assets will not be served. If `auto_start_machines` is enabled, the machine will be started automatically in response to statics requests, the same as with normal application requests.
+- The Fly Proxy routes matching requests directly to the statics file server running inside your machine. Static requests do not go through your main application process, but are handled by the file server within the same VM.
+- Serving an index document is only supported for statics served directly from Tigris. The index document must have the same name in all the relevant locations in your Tigris bucket.
+- For statics served from your container, it will not find `index.html` at the root. The full path must be requested.
+- You cannot set `Cache-Control` or any other headers on assets. If you need those, deliver them from your application and set the relevant headers.
+- `statics` does not honor symlinks. For example, if `/app/public` in your container is a symlink, such as `/app-39403/public`, use the absolute original path in your statics configuration.
-* `statics` does not honor symlinks. So, if `/app/public` in your container is actually a symlink to something like `/app-39403/public`, you'll want to use the absolute original path in your statics configuration.
## The `files` section
-When `files` are set, the contents from one of `raw_value`, `local_path`, or `secret_name` will be written to the Machine at the provided `guest_path`. The contents of both `raw_value` and `secret_name` must be base64 encoded.
+When a `files` section is set, the contents from one of `raw_value`, `local_path`, or `secret_name` will be written to the Machine at the provided `guest_path`.
+
+* `raw_value`: The raw file content to be written. Must be base64 encoded.
+* `local_path`: The path to a local file in your system that should be used. Does not need to be base64 encoded.
+* `secret_name`: The name of an app secret that's been set with `fly secrets set`. The value of the secret will be written to the file. The referenced secret's value must be base64 encoded.
Examples:
@@ -1035,6 +1218,10 @@ Examples:
guest_path = "/path/to/hello.txt"
raw_value = "aGVsbG8gd29ybGQK"
+[[files]]
+ guest_path = "/path/to/config.yaml"
+ local_path = "/local/path/config.yaml"
+
[[files]]
guest_path = "/path/to/secret.txt"
secret_name = "SUPER_SECRET"
diff --git a/reference/content-encoding.html.md b/reference/content-encoding.html.md
new file mode 100644
index 0000000000..0273f6fcff
--- /dev/null
+++ b/reference/content-encoding.html.md
@@ -0,0 +1,107 @@
+---
+title: Content Encoding with the Fly Proxy
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-06-17
+---
+
+
+This page explains how our proxy handles HTTP response compression, so you get better performance out of the box.
+
+
+Fly.io apps often serve a lot of text: HTML, CSS, JavaScript, JSON. Compressing those responses reduces bandwidth and speeds up delivery. Our edge proxy takes care of this automatically. Unless your app explicitly sets a `Content-Encoding` header, we negotiate compression on your behalf based on the client's `Accept-Encoding` header.
+
+This behavior usually Just Works. But there are edge cases you might care about. Here's how it works, what we support, and how to override it if needed.
+
+### What is Content Encoding?
+
+Content encoding compresses HTTP response bodies (the data or content being sent by the server to the client). Clients include an `Accept-Encoding` header listing the algorithms they support. If the server supports any of them, it compresses the response and sets the `Content-Encoding` header to indicate the algorithm used. The client then decompresses the body before rendering it.
+
+Common encodings include:
+
+- `br` (Brotli)
+- `gzip`
+- `zstd` (Zstandard)
+- `deflate`
+
+All of these are lossless. They're not encryption, and they're not the same as chunked transfer encoding—they just make payloads smaller.
+
+### What the Fly.io Proxy Supports
+
+The Fly.io proxy handles compression at the edge, automatically selecting the most efficient algorithm supported by both the client and the proxy. It prefers:
+
+1. **Zstandard (zstd)** — Best all-around for performance and size. Supported in Chrome and Firefox.
+1. **Brotli** — Still excellent, especially for text. Smaller files than gzip in many cases.
+1. **gzip** — The fallback. Widely supported.
+1. **deflate** — Rarely used, but still supported.
+
+The proxy only compresses responses that don’t already include a `Content-Encoding` header. If your app sets one, the proxy passes the response through unmodified. You can also explicitly disable compression by setting `Content-Encoding: none`.
+
+### Configuration
+
+Configure the HTTP handler to enable proxy-level compression:
+
+The http handler must be used in your `fly.toml` file for the service. There are two ways to do this (choose only one!):
+
+- [http_service](/docs/reference/configuration/#the-http_service-section) preconfigures the most common options for a service speaking HTTP. It will use the http handler implicitly for both ports 80 and 443 (and additionally, of course, the tls handler for port 443).
+
+```
+[http_service]
+ internal_port = 8080
+ force_https = true
+ auto_stop_machines = "stop"
+ auto_start_machines = true
+ min_machines_running = 0
+ [http_service.concurrency]
+ type = "requests"
+ soft_limit = 200
+ hard_limit = 250
+```
+
+- A [service](/docs/reference/configuration/#the-services-sections) block explicitly using the http handler. This example serves http only (no https) on external port 80:
+
+```
+[[services]]
+ internal_port = 8080
+ protocol = "tcp"
+ auto_stop_machines = "stop"
+ auto_start_machines = true
+ min_machines_running = 0
+[[services.ports]]
+ handlers = ["http"] # This is the important part
+ port = 80
+```
+
+If you want to also serve external port 443 with https, add this to the above:
+
+```
+[[services.ports]]
+ handlers = ["tls", "http"]
+ port = 443
+```
+
+
+### When You Might Want to Customize Behavior
+
+Most apps can rely on the defaults, but if you're doing something more specific like streaming HTML, here is something to consider:
+
+#### Streaming HTML
+
+The proxy buffers responses before compressing them. That can break HTML streaming—e.g., progressive server-side rendering. To avoid buffering, disable compression by setting `Content-Encoding: none` for those responses:
+
+```http
+HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+Content-Encoding: none
+```
+
+### Summary
+
+Fly.io’s proxy gives you fast, automatic compression without config. You can let it handle things, or take control if you need to. If you're troubleshooting compression knowing how the proxy behaves can help you get the results you want.
+
+More in the docs:
+
+- [fly.toml reference](/docs/reference/configuration/)
+- [How Fly.io handles HTTP](/docs/reference/services/)
+- [Troubleshooting app performance](/docs/apps/performance/)
diff --git a/reference/enveloop.html.md b/reference/enveloop.html.md
deleted file mode 100644
index c1497a10d6..0000000000
--- a/reference/enveloop.html.md
+++ /dev/null
@@ -1,120 +0,0 @@
----
-
-title: Enveloop Email API
-layout: docs
-status: alpha
-nav: firecracker
----
-
-[Enveloop](https://enveloop.com) lets developers design, send, and track email and text messages from Fly applications using a simple API. They can store all message content, images, HTML, and CSS in Enveloop -- keeping Fly apps lightweight.
-
-
-This service is in **public beta** on Fly.io. A couple of notes:
-
-Enveloop is a production service. For our Fly.io launch, we are in public beta as we tighten up the integration.
-
-During the beta period, you won’t be charged for Enveloop usage.
-
-
-## Create an Enveloop project
-
-Creating an Enveloop project happens via the [Fly.io CLI](/docs/hands-on/install-flyctl/). Once the Fly.io CLI is installed, [sign up for a Fly.io account](https://fly.io/docs/getting-started/log-in-to-fly/) and create your first Fly app. From there, you can add an Enveloop project to your app.
-
-Running the following command in a Fly app context (inside an app directory or specifying `-a yourapp`) will create a new Enveloop project and Fly Secrets (sandbox & production) on your app.
-
-```cmd
-flyctl ext enveloop create
-```
-
-```output
-? Select Organization: moon (moon)
-? Choose a name, use the default, or leave blank to generate one:
-Your Enveloop project (quiet-butterfly-6638) is ready. See details and next steps with:
-
-Setting the following secrets on quiet-butterfly-6638:
-ENVELOOP_LIVE_API_KEY: live_**********
-ENVELOOP_SANDBOX_API_KEY: test_**********
-```
-
-## The Enveloop Web Console
-
-Enveloop provides a visual display of your message templates, a template builder, and message logging and monitoring. To access these features, use the `dashboard` command to launch the Enveloop web app:
-
-```cmd
-flyctl ext enveloop dashboard
-```
-
-## Use Enveloop with your Language and Framework
-
-Here are some examples for how to use Enveloop with various languages and frameworks.
-
-* [Rails with ActionMailer](https://docs.enveloop.com/enveloop-api/languages-and-frameworks/ruby-on-rails)
-* [Ruby](https://github.com/enveloophq/enveloop-ruby)
-* [Node.js](https://github.com/enveloophq/enveloop-js)
-* [Python](https://github.com/enveloophq/enveloop-py)
-
-## Pricing and Billing
-
-
-During the public beta, Enveloop is free to use on Fly.io.
-
-
-Enveloop starts at $5 per month. With that, we include 1000 messages per month -- each message over 1000 is $0.005. As you send more messages, individual messages sent cost less -- the breakdown below illustrates. While we only charges for messages (email or text) sent, the $5 minimum helps cover logging, data retention, and support. You can view the official [Enveloop Pricing](https://enveloop.com/pricing) page for additional information.
-
-
-Here is a breakdown of the per message costs, per month, as your usage on Enveloop scales.
-
-* $0.00500 - 1000-5000 (minimum $5 spend)
-* $0.00250 - 5001-50,000
-* $0.00170 - 50,001-100,000
-* $0.00145 - 100,001-500,000
-* $0.00140 - 500,001+
-
-
-
-Enveloop usage fees are billed daily and will show up, throughout the month, on your Fly.io invoice.
-
-
-Enveloop plans include unlimited use of our message builder, templates, analytics, logging, and our webhooks. Also, you can add as many users as you need. These features are accessible via the Enveloop web console.
-
-Additional information about Enveloop capabilities are located in the [Enveloop Docs](https://docs.enveloop.com).
-
-
-### List your project and view project status
-
-Get a list of your Enveloop projects.
-
-```cmd
-flyctl ext enveloop list
-```
-
-```output
-NAME ORG PRIMARY REGION
-quiet-butterfly-6638 moon
-```
-
-```cmd
-flyctl ext enveloop status quiet-butterfly-6638
-```
-
-```output
-Status
- Name = quiet-butterfly-6638
- Status = created
- Region =
-```
-
-### Delete an Enveloop project
-
-Be aware! -- Deleting can't be undone. This will remove all message settings, templates, and message logs.
-
-```cmd
-fly ext enveloop destroy quiet-butterfly-6638
-```
-
-```output
-Destroying an Enveloop project is not reversible.
-
-? Destroy Enveloop project named quiet-butterfly-6638? Yes
-Your Enveloop project quiet-butterfly-6638 was destroyed
-```
diff --git a/reference/fly-proxy-autostop-autostart.html.markerb b/reference/fly-proxy-autostop-autostart.html.markerb
index a6bb66b958..915ee43890 100644
--- a/reference/fly-proxy-autostop-autostart.html.markerb
+++ b/reference/fly-proxy-autostop-autostart.html.markerb
@@ -49,6 +49,18 @@ Fly Proxy determines when to start a Machine as follows:
* If all the running Machines are above their `soft_limit` setting, then the proxy starts a stopped or suspended Machine in the nearest region (if there are any stopped or suspended Machines).
* The proxy routes the request to the newly started Machine.
+### How Fly Proxy behaves without autostart/autostop
+
+Fly Proxy still routes requests to your app’s Machines even when `auto_stop_machines` is not set, or when `auto_start_machines` is explicitly set to `false` in your `fly.toml` file.
+
+- Machines stay running unless they exit on their own or are stopped during a deploy or scaling operation.
+- Fly Proxy does not check for excess capacity or stop Machines automatically, even if they are idle.
+- If `auto_start_machines` is explicitly set to `false`, the proxy only routes traffic to Machines that are already running. If no Machines are running, the proxy does not start one and the request will fail.
+- After a Machine starts, there may be a short delay before all proxy nodes recognize that it is running. During this time, requests sent to the Machine might fail if the proxy tries to route traffic too early.
+- If a Machine becomes unreachable, such as failing repeated internal pings over Fly’s private IPv6 network, it may be marked as unhealthy and stopped. Any requests already routed to that Machine will fail, since it’s no longer considered valid for the app.
+
+We recommend setting `auto_start_machines = true` and `auto_stop_machines = "stop"` or `"suspend"` in your `fly.toml` file to reduce idle resource usage and improve availability for apps that need to scale based on demand.
+
## Related topics
- [Configure autostop/autostart for Fly Launch apps](/docs/launch/autostop-autostart/)
diff --git a/reference/health-checks.html.markerb b/reference/health-checks.html.markerb
new file mode 100644
index 0000000000..0c733bbe84
--- /dev/null
+++ b/reference/health-checks.html.markerb
@@ -0,0 +1,71 @@
+---
+title: Health Checks
+layout: docs
+nav: firecracker
+---
+
+
+
+
+
+Health checks are a useful way to monitor the state of your app on Fly.io. When configured, they help the platform make decisions about routing traffic and managing deployments. For example, health checks can:
+
+- Confirm that Machines are ready before receiving traffic
+- Route around unhealthy Machines to maintain availability
+- Halt or roll back deployments when a new version isn't responding correctly
+
+Fly.io supports several types of health checks for different use cases. These are configured in your fly.toml file, in the relevant section depending on the type of check.
+
+## Monitoring your health checks
+
+You can view your app's current health check status using the `fly checks list` command. While checks run periodically, the Last Updated column shows when the check's status last changed, not when it was last executed.
+
+
+**Important:** A failing health check can prevent request routing to your Machine. However your Machines won't automatically restart or stop due to failing their health checks, this needs to be done manually.
+
+
+
+## Top-level checks
+
+Top-level health checks, defined in the `[checks]` section of your fly.toml file, are designed for monitoring the overall health of your application, especially for non-public-facing services. Unlike service-level health checks (e.g., `services.http_checks` or `services.tcp_checks`), which can be used to direct traffic away from unhealthy Machines, top-level checks do not affect request routing. This makes them suitable for internal monitoring and alerting purposes without impacting how traffic is distributed across your Machines.
+
+### Custom Service Checks
+
+Use custom checks when you need specific configurations or headers, such as checking authenticated or private endpoints. You can configure custom checks for both TCP and HTTP service checks.
+
+For more information on custom checks, check out the [checks section](/docs/reference/configuration/#the-checks-section).
+
+## Service-level checks
+
+Service-level checks let you define how the proxy determines if a Machine is ready to serve traffic. These checks run at the proxy level and prevent traffic from being sent to unhealthy or slow-starting Machines. TCP checks confirm that your app is listening on the expected port. HTTP checks go further by making a request to a specific path and expecting a 2xx HTTP response. This helps catch cases where the app has started but isn't ready to serve real traffic yet. Using both types of checks can help catch different kinds of issues: TCP for basic reachability, HTTP for readiness.
+
+The proxy uses service-level checks to determine whether a given Machine should be routed to. If a Machine fails its service check, it will be marked as unhealthy by the proxy, and it won't be routed to until its checks start passing.
+
+While service-level checks affect routing availability, a failing check won't cause the Machine to restart or stop.
+
+Service-level checks are configured using the [`[[services.tcp_checks]]`](/docs/reference/configuration/#services-tcp_checks), [`[[services.http_checks]]`](/docs/reference/configuration/#services-http_checks) and [`[[http_service.checks]]`](/docs/reference/configuration/#http_service-checks) sections.
+
+## Health checks for apps with no services
+
+Some apps don’t run long-lived services. Job runners, queue workers, and similar workloads might start up, do their work, and shut down, or run continuously without exposing a network service. These apps can't use Fly’s built-in service-level health checks, but you can still monitor them using indirect signals.
+
+One common approach is to alert on queue depth. If messages pile up, your workers might be down or in a stuck state. You can also poll the Machines API to check whether instances are up, or run a periodic watchdog job and verify it executes as expected. Tools like [Dead Man’s Snitch](https://deadmanssnitch.com/) can help here.
+
+Some worker stacks, such as [Flower](https://flower.readthedocs.io/en/latest/) for Celery, Sidekiq with a [Prometheus exporter](https://github.com/Strech/sidekiq-prometheus-exporter), or BullMQ with [Prometheus metrics](https://docs.bullmq.io/guide/metrics/prometheus), expose internal health signals you can alert on. These don’t act as health checks in the Fly routing sense, but they give you observability into your worker fleet.
+
+## Machine checks
+
+Machine health checks run only during deploys and are supported for apps using the `rolling` and `canary` deployment strategies. They run a custom command inside an ephemeral Machine and can be used to verify app behavior beyond port or endpoint availability. They're useful for validating app readiness in more complex scenarios, such as confirming database connectivity or verifying that a key background service is up. These checks don't impact routing, but if a Machine check fails, the deployment will be stopped.
+
+Machine checks are configured using the [`[[services.machine_checks]]`](/docs/reference/configuration/#services-machine_checks) and [`[[http_service.machine_checks]]`](/docs/reference/configuration/#the-http_service-machine_checks-section) sections.
+
+## Bluegreen checks
+
+Bluegreen checks are an internal check used as part of bluegreen deployments. These run automatically when using a bluegreen deployment strategy, and you may see them listed in the output of `fly checks list` as `bg_deployment`.
+
+You don't need to configure them yourself, and they don't impact routing after the deployment has completed.
+
+## Related topics
+
+- [App configuration (fly.toml)](/docs/reference/configuration/)
+- [Seamless/Zero-downtime deployments](/docs/blueprints/seamless-deployments/)
diff --git a/reference/index.html.md b/reference/index.html.md
index 63df861665..65468553a1 100644
--- a/reference/index.html.md
+++ b/reference/index.html.md
@@ -28,6 +28,12 @@ Quick references for often-used resources like flyctl and `fly.toml`. Or dig a l
* **[Fly Proxy autostop/autostart](/docs/reference/fly-proxy-autostop-autostart/):** Learn how Fly Proxy determines excess capacity for an app to shut down or suspend Machines when they're not needed and start them back up when there's traffic.
+* **[Fly Proxy Content Encoding](/docs/reference/content-encoding/):** Learn how our proxy handles HTTP response compression, so you get better performance out of the box.
+
+* **[Machine Suspend/Resume](/docs/reference/suspend-resume/):** How to pause a Fly Machine in place and resume it in milliseconds.
+
+* **[Health Checks](/docs/reference/health-checks/):** Learn how Fly monitors your apps health through HTTP, TCP, and custom checks to verify availability and trigger automatic restarts when needed.
+
---
## Working with Fly.io
@@ -38,4 +44,6 @@ Quick references for often-used resources like flyctl and `fly.toml`. Or dig a l
* **[Load Balancing](/docs/reference/load-balancing/):** How Fly Proxy distributes traffic to your application instances based on load, closeness, and concurrency settings.
+* **[Migrating from AWS to Fly.io overview](/docs/reference/aws-to-fly-guide/):** A guide with considerations for moving from AWS to Fly.io.
+
* **[Multiple processes inside a Fly.io app](/docs/app-guides/multiple-processes/):** The different ways to run multiple processes inside a Fly App.
diff --git a/reference/load-balancing.html.md b/reference/load-balancing.html.md
index 300712ca66..033d7fced0 100644
--- a/reference/load-balancing.html.md
+++ b/reference/load-balancing.html.md
@@ -25,6 +25,10 @@ The table below describes how traffic may or may not be routed to a Machine base
| At or above `soft_limit`, below `hard_limit` | Traffic will only be sent to this Machine if all other Machines are also above their `soft_limit` |
| Below `soft_limit` | Traffic will be sent to the Machine when it is the closest Machine that is under `soft_limit` |
+
+Cross-region routing only happens when all Machines in the local region are unhealthy or at their `hard_limit`.
+
+
### Closeness
Closeness is determined by RTT (round-trip time) between the Fly.io edge server receiving a connection or request, and the worker server where your Machine runs. Even within the same region, we use different datacenters with different RTTs. These RTTs are measured constantly between all servers.
@@ -44,14 +48,14 @@ We have a hypothetical web service that we know can handle 25 concurrent request
We set `type = "requests"` so Fly.io will use concurrent HTTP requests to determine when to adjust load. We prefer this to `type = "connections"`, because our web service does work for each request and our users may make multiple requests over a single connection (e.g., with HTTP/2). Fly Proxy will also pool connections to a Machine for a short time (about 4 seconds) when using `type = "requests"` to avoid frequent opening and closing of connections to your app.
-We set the `soft_limit` to 20, so we have a little room for Fly Proxy to shift load to other Machines before a single Machine becomes overwhelmed.
+We set the `soft_limit` to 20, so Fly Proxy has some headroom to prefer less-loaded Machines within the same region before distributing traffic more evenly. Soft limits only affect routing within a region. They do not cause the proxy to shift traffic to other regions.
We deploy 10 Machines in four regions: `ams` (Amsterdam), `bom` (Mumbai), `sea` (Seattle), and `sin` (Singapore), with three of those in `ams`.
In this contrived example, all of the users are currently in Amsterdam, so the traffic is arriving at one of the Fly.io edges in Amsterdam. Here's what happens as the number of concurrent HTTP requests from users in Amsterdam increases:
- 60 concurrent requests: Requests are divided evenly between the 3 Machines in the `ams` region. Closeness of the worker and edge will determine which of the 3 Machines each request goes to.
-- 61+ concurrent requests: 60 of those requests will be sent to the 3 Machines in the `ams` region and the rest will be sent to the closest Machines in other regions.
+- 61+ concurrent requests: If all three Machines in `ams` are above their `soft_limit`, but still below their `hard_limit`, Fly Proxy continues routing to them. It does not route traffic to other regions based on soft limits alone.
- 200+ concurrent requests: All Machines are at their `soft_limit`, so Fly Proxy will start routing requests to the `ams` Machines again. For example, the 201st concurrent request will go to a Machine in the `ams` region that is currently at its `soft_limit`.
- 250 concurrent requests: All Machines are at their `hard_limit`. The 251st concurrent request will get queued by Fly Proxy until a Machine is below its `hard_limit`.
diff --git a/reference/machine-migration.html.markerb b/reference/machine-migration.html.markerb
index 54827e2354..429e63e2db 100644
--- a/reference/machine-migration.html.markerb
+++ b/reference/machine-migration.html.markerb
@@ -4,6 +4,10 @@ layout: docs
nav: firecracker
---
+
+
+
+
Fly.io will migrate your Machines from problematic hosts to healthy hosts as required. Some of the reasons for migrating Machines include host overcrowding, deprecation, or scheduled maintenance. The migration process helps your applications run efficiently on the Fly.io platform. You don't need to take any action to migrate your Machines to another host and, if your app has [multiple Machines](/docs/blueprints/resilient-apps-multiple-machines/), migration shouldn't result in any downtime for your app.
## Machine migration process
diff --git a/reference/regions.html.markerb b/reference/regions.html.markerb
index e6cd50174f..da0c6c33d8 100644
--- a/reference/regions.html.markerb
+++ b/reference/regions.html.markerb
@@ -4,14 +4,16 @@ layout: docs
nav: firecracker
---
-Fly.io runs applications physically close to users: in datacenters around the world, on servers we run ourselves. You can currently deploy your apps in 35 regions, connected to a global Anycast network that makes sure your users hit our nearest server, whether they’re in Tokyo, São Paulo, or Amsterdam.
+
+
+
+
+Fly.io runs applications physically close to users: in datacenters around the world, on servers we run ourselves. You can deploy apps in regions worldwide, so your users in Tokyo, São Paulo, or Amsterdam connect to the nearest server through our global Anycast network.
Run fly platform regions to get a list of regions.
-
-
## Fly.io Regions
You can host your apps in any of the following regions.
@@ -19,61 +21,42 @@ You can host your apps in any of the following regions.
| Region ID | Region Location | Gateway |
|-----------|-------------------------------|-------------|
| ams | Amsterdam, Netherlands | ✓ |
-| arn | Stockholm, Sweden | |
-| atl | Atlanta, Georgia (US) | |
-| bog | Bogotá, Colombia | |
-| bom | Mumbai, India | |
-| bos | Boston, Massachusetts (US) | |
+| arn | Stockholm, Sweden | ✓ |
+| bom | Mumbai, India | ✓ |
| cdg | Paris, France | ✓ |
-| den | Denver, Colorado (US) | |
| dfw | Dallas, Texas (US) | ✓ |
-| ewr | Secaucus, NJ (US) | |
-| eze | Ezeiza, Argentina | |
+| ewr | Secaucus, NJ (US) | ✓ |
| fra | Frankfurt, Germany | ✓ |
-| gdl | Guadalajara, Mexico | |
-| gig | Rio de Janeiro, Brazil | |
| gru | Sao Paulo, Brazil | |
-| hkg | Hong Kong, Hong Kong | ✓ |
| iad | Ashburn, Virginia (US) | ✓ |
| jnb | Johannesburg, South Africa | |
| lax | Los Angeles, California (US) | ✓ |
| lhr | London, United Kingdom | ✓ |
-| mad | Madrid, Spain | |
-| mia | Miami, Florida (US) | |
| nrt | Tokyo, Japan | ✓ |
| ord | Chicago, Illinois (US) | ✓ |
-| otp | Bucharest, Romania | |
-| phx | Phoenix, Arizona (US) | |
-| qro | Querétaro, Mexico | |
-| scl | Santiago, Chile | ✓ |
-| sea | Seattle, Washington (US) | ✓ |
| sin | Singapore, Singapore | ✓ |
| sjc | San Jose, California (US) | ✓ |
| syd | Sydney, Australia | ✓ |
-| waw | Warsaw, Poland | |
-| yul | Montreal, Canada | |
| yyz | Toronto, Canada | ✓ |
- **Gateway regions:** "Gateway" regions also have WireGuard gateways, through which you connect to your organization's private network.
-## Edge-only regions
-
-Edge servers receive inbound traffic from the Internet destined for apps on the Fly.io platform and private traffic between Machines in different regions. [Fly Proxy](/docs/reference/fly-proxy/) routes traffic from edges to regional worker servers. Edge-only regions don't have any workers and therefore aren't available for app deployment.
-
-| Region ID | Region Location |
-|-----------|-------------------------------|
-| dxb | Dubai, United Arab Emirates |
-| ist | Istanbul, Turkey |
-| mel | Melbourne, Australia |
-
-You might see an edge-only region code on your invoices for egress bandwidth or in the `Fly-Region` request header.
-
## Discovering your app's region
-View the list of Fly.io regions with [`fly platform regions`](https://fly.io/docs/flyctl/platform-regions).
+View the list of Fly.io regions with [`fly platform regions`](/docs/flyctl/platform-regions).
-You can see which regions your app is running in with [`fly status`](https://fly.io/docs/flyctl/status/).
+You can see which regions your app is running in with [`fly status`](/docs/flyctl/status/).
[Fly Volumes](/docs/volumes/) and [Fly Machines](/docs/machines/) are tied to the region they're created in.
+Learn more about Machine placement and regional capacity in this [guide](/docs/machines/guides-examples/machine-placement/).
+
When an application instance is started, the three-letter name for the region it's running in is stored in the Machine's `FLY_REGION` environment variable. This, along with other [Runtime Environment](/docs/machines/runtime-environment/) information, is visible to your app running on that instance.
+
+## Related reading
+
+- [Scaling to multiple regions](/docs/blueprints/resilient-apps-multiple-machines/#scaling-to-multiple-regions) Read our guide for making your app resilient and globally distributed.
+- [Cost Management](/docs/about/cost-management/) Find out how region choice, scaling strategy, and app architecture affect your Fly.io bill.
+- [Machine Placement and Regional Capacity](/docs/machines/guides-examples/machine-placement/) Learn more about how the choice of region (and region capacity) affects where your Machines land.
+- [Dynamic Request Routing with fly‑replay](/docs/networking/dynamic-request-routing/) Check out our guide to routing requests to specific regions or fallbacks using region codes.
+
diff --git a/reference/suspend-resume.html.md b/reference/suspend-resume.html.md
new file mode 100644
index 0000000000..54dab5eb3f
--- /dev/null
+++ b/reference/suspend-resume.html.md
@@ -0,0 +1,209 @@
+---
+title: Machine Suspend and Resume
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-08-15
+---
+
+**Machine suspend** lets you pause a running Fly Machine and save its complete state, including memory, to persistent storage. When resumed, the machine picks up exactly where it left off, without rebooting the OS or restarting your app. That can make startup take just **hundreds of milliseconds** instead of multiple seconds.
+
+You can think of suspend as what a laptop does when you close the lid, except your “laptop” is a microVM running in, say, `dfw` or `fra` or `syd`.
+
+## How it works
+
+Suspend uses [Firecracker snapshots](https://firecracker-microvm.github.io/) to capture the entire VM state: CPU registers, memory contents, open file handles. When you start a suspended machine, Fly restores from this snapshot instead of cold booting.
+
+**Typical performance:**
+
+- Resume from suspend: a few hundred ms
+- Cold start: ~2+ seconds for common apps
+- TCP connections may survive if the remote side keeps them open
+
+---
+
+## Using Suspend
+
+### Manually
+
+```bash
+# Suspend a machine
+fly machine suspend
+
+# Check status (running, suspending, suspended, etc.)
+fly machine status
+
+# Resume from snapshot
+fly machine start
+
+# Force a cold start (discard snapshot)
+fly machine stop
+fly machine start
+```
+
+### Automatically via Fly Proxy
+
+Configure in `fly.toml`:
+
+```
+[http_service]
+ auto_stop_machines = "suspend" # or "stop"
+ auto_start_machines = true
+
+ [[http_service.concurrency]]
+ type = "requests"
+ soft_limit = 25
+```
+
+The proxy will automatically suspend machines during low traffic, checking for idle periods every few minutes, and resume them when requests arrive.
+
+### Machines API
+
+```
+# Suspend
+POST /v1/apps/{app_name}/machines/{machine_id}/suspend
+
+# Wait for suspension to complete
+GET /v1/apps/{app_name}/machines/{machine_id}/wait?state=suspended
+
+# Resume (standard start endpoint)
+POST /v1/apps/{app_name}/machines/{machine_id}/start
+```
+
+Generally, you need an API token to use the Machines API. But if you're just suspending _your own_ machine, you can skip the token and hit the `/.fly/api` Unix socket directly:
+
+```bash
+$ curl --unix-socket /.fly/api -X POST \
+ http://flaps/v1/apps/$FLY_APP_NAME/machines/$FLY_MACHINE_ID/suspend
+```
+
+---
+
+## Requirements
+
+A machine can use suspend if it has:
+
+- **≤ 2 GB** memory (For larger memory sizes, suspend is discouraged due to increased suspend times)
+- **No** [**swap**](https://fly.io/docs/reference/configuration/#swap_size_mb-option) **configured**
+- **No** [**schedule**](https://fly.io/docs/machines/flyctl/fly-machine-run/#start-a-machine-on-a-schedule) **configured**
+- **No GPU configured**
+- Been updated since **June 20, 2024 20:00 UTC**
+
+If you have an older machine, or you’re not sure when it was last updated, you can bring it up to date with:
+
+```bash
+fly machine update --yes
+```
+
+This updates the machine in place to the latest supported configuration for suspend, without changing your app code or image.
+
+---
+
+## Limitations and considerations
+
+- Suspend is not currently recommended for large machine memory sizes (> 2 GB)
+- Suspending many machines at once is not recommended
+- Some logs may be lost after resume
+- Unlike stop, suspend **does not** reset the machine's `rootfs`
+- On resume, the clock can lag a few seconds until NTP syncs
+
+
+Always design for both resume and cold start paths.
+
+
+---
+
+## Snapshot behavior with suspend
+
+
+Snapshots are tied to the exact code and state of the machine they were taken from. If you deploy new code, the old snapshot can’t be resumed safely and will be discarded.
+
+
+**Snapshots** **aren’t guaranteed to persist.** Cold starts may happen if:
+
+- **You deploy a new version of your app** — deployments rebuild the machine image, which invalidates the old snapshot. Since a snapshot is a literal memory dump of the _old_ process, resuming it after you’ve swapped in new code or dependencies would be unsafe and unpredictable.
+- The machine is migrated to a different host
+- The snapshot file is lost or corrupted — Hardware failures, space reclamation, or corruption can cause them to be deleted
+- We perform system maintenance or updates
+
+---
+
+## Handling Network Connections After Resume
+
+On resume, the machine thinks its network connections are still live. External systems (databases, APIs) may disagree.
+
+Common symptoms:
+
+- `ECONNRESET`
+- "Connection closed"
+- Timeouts on first request
+- Database pool errors
+
+**Fix:** Reconnect on failure.
+
+Example (Python + DB):
+
+```python
+try:
+ result = db.execute(query)
+except (ConnectionError, OperationalError):
+ db.reconnect()
+ result = db.execute(query)
+```
+
+Tips:
+
+- Use connection pools with disconnect handling (see this excellent [SQLAlchemy guide](https://docs.sqlalchemy.org/en/20/core/pooling.html#dealing-with-disconnects))
+- Shorten connection timeouts to fail fast
+- Use retry/backoff for HTTP clients
+- Test after long suspensions
+
+---
+
+## Billing
+
+Suspended machines cost the same as stopped machines: storage only. There are no CPU/RAM charges.
+
+---
+
+## Monitoring & Debugging
+
+```bash
+fly machine status
+```
+
+States:
+
+- `running`
+- `suspending`
+- `suspended`
+- `starting` (resume or cold start)
+- `stopped`
+
+If machines cold start unexpectedly:
+
+- Check requirements
+- Confirm no migrations or deployments occurred
+- Check logs for suspend/resume events
+
+Test cold start:
+
+```bash
+fly machine stop
+fly machine start
+```
+
+---
+
+## Availability
+
+Suspend works in **all Fly.io regions** as of July 2024.
+
+---
+
+**Related reading:**
+
+- [Autostop & Autostart](/docs/launch/autostop-autostart/)
+- [Fly Proxy Config](/docs/reference/fly-proxy-autostop-autostart/)
+- [Scaling Machines](/docs/apps/scale-count/)
+- [Machines API](https://docs.machines.dev/)
diff --git a/rust/frameworks/dioxus-liveview.html.markerb b/rust/frameworks/dioxus-liveview.html.markerb
index 6914a8fd1b..c2e2b3118b 100644
--- a/rust/frameworks/dioxus-liveview.html.markerb
+++ b/rust/frameworks/dioxus-liveview.html.markerb
@@ -9,7 +9,7 @@ order: 6
---
-<%= partial "/docs/languages-and-frameworks/partials/intro", locals: { runtime: "Dioxus Liveview", link: "https://dioxuslabs.com/learn/0.5/getting_started/liveview/" } %>
+<%= partial "/docs/languages-and-frameworks/partials/intro", locals: { runtime: "Dioxus Liveview", link: "https://dioxuslabs.com/learn/0.6/getting_started/" } %>
Dioxus Liveview allows apps to run on the server and render in the browser. It uses WebSockets to communicate between the server and the browser.
diff --git a/rust/index.html.md b/rust/index.html.md
index 8053887ddc..5c62bfe8bd 100644
--- a/rust/index.html.md
+++ b/rust/index.html.md
@@ -7,6 +7,10 @@ redirect_from:
- /docs/getting-started/rust/
---
+
+
+
+
Fly is a great place to launch your Rust applications, especially if you plan on running them on multiple servers around the world so your users have a fast, snappy, low-latency experience.
If you have questions or comments about running Rust applications on Fly.io, create a new topic in our [community forum](https://community.fly.io/) and tag it with "rust" so the right people can give you the support you need.
diff --git a/security/arcjet.html.md b/security/arcjet.html.md
index 4c37fc8779..9470f7893b 100644
--- a/security/arcjet.html.md
+++ b/security/arcjet.html.md
@@ -161,9 +161,7 @@ Load the application and refresh the page a few times to see the rate limit in a
## Pricing
-Arcjet is currently in beta and is free to use. The current features available today will be unlimited and free to use. Our goal is to help developers protect their applications so we don’t want you to incur costs if you are attacked.
-
-Arcjet pricing will be based on the usage of features we intend to introduce in the future e.g. organization-wide rules, compliance tools, team management, etc.
+See [the Arcjet pricing page](https://arcjet.com/pricing).
## Support
diff --git a/security/index.html.markerb b/security/index.html.markerb
index 82d2d01dd6..20a163c50c 100644
--- a/security/index.html.markerb
+++ b/security/index.html.markerb
@@ -17,6 +17,7 @@ Securing a public cloud platform like Fly.io is a hard problem, and we take it s
_Security for customer organizations and apps._
+- **[Organization Roles and Permissions](/docs/security/org-roles-permissions/):** Understand what Members and Admins can do in a Fly.io organization, and how to manage access safely.
- **[Use SSO for organizations](/docs/security/sso/):** Set up org-wide Single Sign-on with Google or GitHub.
- **[Remove a member from an organization](/docs/security/remove-org-member/):** Remove a user from an organization and take steps to help keep the organization secure.
- **[Built-in TLS termination](/docs/security/tls-termination/):** You get TLS termination by default for your web apps.
diff --git a/security/org-roles-permissions.html.md b/security/org-roles-permissions.html.md
new file mode 100644
index 0000000000..34d2eeb6c6
--- /dev/null
+++ b/security/org-roles-permissions.html.md
@@ -0,0 +1,81 @@
+---
+title: Organization Roles and Permissions
+layout: docs
+nav: firecracker
+author: kcmartin
+date: 2025-08-04
+---
+
+## Organization Role Types
+
+Fly.io organizations (orgs) have two roles: **Member** and **Admin**. These roles determine what users can do within your org, from deploying apps to managing billing.
+
+### Member
+
+Most people on your team will be Members. They can do everything you'd expect a developer or engineer to need:
+
+- Create and destroy apps
+- Deploy new versions
+- Manage secrets, volumes, and machines
+- View logs and metrics
+- Access the org's private network
+
+What they _can’t_ do: change anything about the org itself. No inviting or removing users, no billing access, no deleting the org.
+
+### Admin
+
+Admins have all the permissions of Members, plus a few critical ones:
+
+- Invite and remove users
+- View and update billing info
+- Permanently delete the organization
+
+Use the admin role sparingly. Admins can burn the place down—on purpose or by accident.
+
+**Note**: For billing and user permission details for our extension partners (e.g. Tigris, Sentry, etc.) please check with the vendor directly.
+
+### Quick Comparison
+
+| Capability | Member | Admin |
+| --- | --- | --- |
+| Deploy & manage apps | ✓ | ✓ |
+| Invite/remove users | X | ✓ |
+| Manage billing | X | ✓ |
+| Delete the org | X | ✓ |
+
+## Strategies for Safer Access Control
+
+Fly.io keeps roles simple on purpose: you get Admins and Members. But that doesn’t mean you’re stuck with a binary choice between “can delete the org” and “can’t deploy apps.” Here are a couple of ways teams carve out safer access patterns using the tools we already support.
+
+### 1. Split Environments into Separate Organizations
+
+Fly.io organizations are free to create, and there's no technical limit to how many you can have. One thing to note, each organization needs a payment method or a linked billing organization. You can read more about linked organizations and billing [here](/docs/about/billing/#unified-billing).
+
+Let’s say you run a multiplayer game platform. You might set up:
+
+- `arcadia-dev` where every engineer is an Admin, free to deploy test builds, tweak game servers, and experiment with new features.
+- `arcadia-prod` where only ops and senior engineers have Admin rights, and everyone else is a Member.
+
+This setup avoids the classic “accidental prod deploy” scenario. Admins still get full control where it matters, but you’re not handing out the keys to production just so someone can push a staging build.
+
+### 2. Use Read-Only Tokens for Observability-Only Roles
+
+Sometimes people need to see things without touching them. Maybe your support team wants to look at logs to troubleshoot customer issues. Maybe your SREs want to hook into Grafana or another dashboard tool.
+
+You don’t need to make those people Members. Instead, generate a read-only API token:
+
+`flyctl tokens create readonly`
+
+This token can authenticate to Fly.io, but not make any changes. To use it properly, the user should be **logged out** (`fly auth logout`) and run commands with the token set via `FLY_API_TOKEN`. For maximum safety, don’t make these users Members of the org at all. If they are, they could still run `fly auth login` and get full access through their user account, bypassing the token’s restrictions. It’s a great way to integrate observability tools or give your team visibility without risk. You can read more about uses for `fly tokens create` on the [reference page](/docs/flyctl/tokens-create/).
+
+## Summing up
+
+Fly.io keeps roles simple on purpose, but you still have the tools to shape access around how your team actually works. Keep the number of Admins small, make sure Members have only the access they need, and lock down everything else by default.
+
+## Related Reading
+
+- [Moving an application between orgs](/docs/apps/move-app-org/)
+- [Handing over an application](/docs/apps/app-handover-guide/)
+- [Read-only tokens reference](/docs/flyctl/tokens-create-readonly/)
+- [Staging and production isolation](/docs/blueprints/staging-prod-isolation/)
+
diff --git a/styles/Fly/WordList.yml b/styles/Fly/WordList.yml
index ac8a288865..7f618286d8 100644
--- a/styles/Fly/WordList.yml
+++ b/styles/Fly/WordList.yml
@@ -21,7 +21,7 @@ swap:
data are: data is
datatype: data type
datastore: data store
- filesystem: file system
+ # filesystem: file system
firewalls: firewall rules
free tier: free allowances
functionality: capability|feature
diff --git a/styles/config/vocabularies/fly-terms/accept.txt b/styles/config/vocabularies/fly-terms/accept.txt
index 7c9748e21f..f7aeee2ef9 100644
--- a/styles/config/vocabularies/fly-terms/accept.txt
+++ b/styles/config/vocabularies/fly-terms/accept.txt
@@ -1,17 +1,23 @@
-APIs?\b
+Alertmanager
+(?i)allowlist
+AMIs?\b
Anycast
+APIs?\b
(?i)Arcjet
Astro
+[Aa]sync
+auditable
autogenerated?\b
automagically
autosav\w+
-(?i)autostart\w+
-(?i)autostop\w+
-(?i)autosuspend\w+
+(?i)autostart\b
+(?i)autostop\b
+(?i)autosuspend\b
backhaul
bluegreen
bool
-buildpacks?\b
+(?i)boolean
+[Bb]uildpacks?\b
bursty
callouts?\b
centiseconds
@@ -19,28 +25,36 @@ cgroup
cgroups
cleartext
containerd
-cron
+[Cc]ron(jobs?|job)?\b
+[Cc]rontabs?\b
datacenters?\b
datasource
datatypes?\b
+declaratively
+(?i)dbs?\b
discoverability
Debian
dev
+devs
(?i)Dockerfile
Dyno
+easycron
+[Ee]nqueues?\b
enum
(?i)env
Escript
Expr
+facto
failover
Fastify
-Fly.io
+[Ff]ly.io
Flycast
flyctl
flyd
Flysystem
fly.toml
Gemfile
+geospatial
GPUs?\b
Grafana
Gunicorn
@@ -50,7 +64,7 @@ Hashicorp
Hasura
hellofly
heredoc
-hostname
+hostnames?\b
Hotwire
idempotently
IEx
@@ -61,6 +75,7 @@ IPSec
ISPs?\b
json
JSON
+kcmartin
kubeconfig
kubectl
Laravel
@@ -75,6 +90,8 @@ microVMs?\b
middleware
Midjourney
Minecraft
+monorepo
+mothership
MTUs?\b
nameservers?\b
namespaces?\b
@@ -88,20 +105,25 @@ Nuxt
NVIDIA
NVMe
OAuth
+Oban
Ollama
orgs?\b
Paketo
-param
+(?i)param(eter)?s?\b
passthrough
+Paulo
PCIe
performant
+PGBouncer
Phusion
plaintext
-pooler
+(?i)pooler
PostgreSQL
(?i)preauthorizations?\b
precompile
preconfigured?\b
+preload(ed)?\b
+(?i)prepend(ing)?
Prisma
(?i)procfiles?\b
Prometheus
@@ -111,25 +133,32 @@ repmgr
repo
Resque
Rio de Janeiro
+rollout
rootfs
RTT
-runtimes?\b
+[Rr]untimes?\b
Ruygt
-São Paulo
+SDKs?\b
(?i)sequelize
(?i)serverless
Sidekiq
signup
+SLAs?\b
(?i)speedrun
+SQLAlchemy
SSH
stdin
stdout
subcommands?\b
subfolders?\b
+subnets?\b
subsecond
Supabase
+Supercronic
swapfile
Symfony
+templated
+Terraform('s)?\b
Tigris
toolkits?\b
tracebacks?\b
@@ -193,5 +222,6 @@ waw
Ashburn
Rio de Janeiro
-São Paulo
+[Ss][ãa]o\b
+[Ss][ãa]o Paulo
Secaucus
diff --git a/styles/words2ignore.txt b/styles/words2ignore.txt
index a26344c8bb..98f35c62b7 100644
--- a/styles/words2ignore.txt
+++ b/styles/words2ignore.txt
@@ -14,6 +14,7 @@ dockerignore
healthcheck
healthchecks
heredocs
+IOPs
nomad/v1
num
postgres
@@ -27,3 +28,5 @@ vm
vms
http
youtube
+shopify
+unavailabilty
diff --git a/upstash/kafka.html.md b/upstash/kafka.html.md
deleted file mode 100644
index 99b19216d1..0000000000
--- a/upstash/kafka.html.md
+++ /dev/null
@@ -1,97 +0,0 @@
----
-title: Upstash Kafka
-layout: docs
-nav: firecracker
-redirect_from: /docs/reference/kafka/
----
-
-
-This service is in public beta in the `iad` and `fra` regions. We don't recommend using it in production, but highly encourage testing with real workloads. Help us make this service shine!
-
-
-[Upstash Kafka](https://docs.upstash.com/kafka) is a fully-managed, pay-as-you-go Kafka service hosted on Fly.io infrastructure.
-
-## Create and manage a Kafka cluster
-
-Creating and managing clusters happens exclusively via the [Fly CLI](/docs/flyctl/install/). Install it, then [signup for a Fly account](/docs/getting-started/sign-up-sign-in/).
-
-### Create and get status of a Kafka cluster
-
-```cmd
-flyctl ext kafka create
-```
-```output
-? Select Organization: fly-apps (fly-apps)
-? Choose a name, use the default, or leave blank to generate one: my-kafka-cluster
-? Choose a primary region (can't be changed later) Madrid, Spain (mad)
-
-Your Upstash cluster (my-kafka-cluster) in iad is ready.
-
-Set the following secrets on your target app.
-
-KAFKA_PASSWORD: MThl0io456uil345u-jkh34-kuj
-KAFKA_USERNAME: Y02kghq4ka345uj0-kl340hkl23
-TCP_ENDPOINT: my-kafka-cluster-kafka.upstash.io:9092
-```
-
-### The Upstash web console
-
-To view more details about your cluster, including usage, run:
-
-```cmd
-flyctl ext kafka dashboard -o
-```
-
-### List your clusters and view status
-Get a list of all of your Kafka clusters.
-
-```cmd
-flyctl ext kafka list
-```
-```output
-NAME ORG PRIMARY REGION
-late-waterfall-1133 fly-apps mad
-```
-
-Fetch status for a cluster:
-
-```cmd
-fly ext kafka status late-waterfall-1133
-```
-```output
-RStatus
- Name = late-waterfall-1133
- Status = created
-```
-
-### Using your Kafka cluster
-
-To start using your cluster, you can create Kafka topics via the [Upstash console](https://upstash.com/docs/kafka/overall/getstarted#create-a-topic) or through a Kafka client connecting from your Fly.io organization.
-
-Check Upstash documentation on [creating a topic](https://upstash.com/docs/kafka/overall/kafkaapi#create-a-topic), [producing messages](https://upstash.com/docs/kafka/overall/kafkaapi#produce-a-message) and [consuming messages](https://upstash.com/docs/kafka/overall/kafkaapi#consume-messages).
-
-
-### Delete a Kafka cluster
-
-Deleting a Kafka cluster can't be undone. Be careful!
-
-```cmd
-fly ext kafka destroy my-cluster
-```
-```output
-Your Kafka cluster my-cluster was deleted
-```
-
-## What you should know
-
-Once provisioned, the cluster primary region cannot be changed.
-
-Your Upstash Kafka is accessible only via a [private IPv6 address](/docs/networking/flycast/) restricted to your Fly.io organization's network.
-
-**To ensure low latency connections, provision your cluster in the same region as your application.**
-
-## Pricing
-
-Upstash Kafka clusters are billed by usage on a pay-as-you-go basis. Check the official [Upstash Pricing](https://upstash.com/pricing/kafka) page for details.
-
-Your usage is visible on your current Fly.io bill, and updated periodically through the day. See detailed cluster usage details in the [Upstash web console](#the-upstash-web-console).
diff --git a/upstash/redis.html.md b/upstash/redis.html.md
index 239d250132..732be44b09 100644
--- a/upstash/redis.html.md
+++ b/upstash/redis.html.md
@@ -5,6 +5,10 @@ nav: firecracker
redirect_from: /docs/reference/redis/
---
+
+
+
+
*Redis is a registered trademark of Redis Ltd. Any rights therein are reserved to Redis Ltd. Any use by Fly.io is for referential purposes only and does not indicate any sponsorship, endorsement or affiliation between Redis and Fly.io.
@@ -118,7 +122,7 @@ Upstash Redis is available in all Fly regions via a [private IPv6 address](/docs
### Writing to replica regions
-**Replicas forward writes to the primary**. Replicas can't be written to. Writes are synchronous, and synchronous writes over geographical distance experience latency. Plan for this latency in your application design.
+**Replicas forward writes to the primary**. Replicas can't be written to. Writes are synchronous, and synchronous writes over geographic distance experience latency. Plan for this latency in your application design.
If you're using Redis as region-local cache and don't require a shared cache, setup separate databases per-region and enable object eviction.
@@ -138,7 +142,7 @@ Upstash offers a range of payment plans for different use cases.
Upstash Redis databases start on the pay-as-you-go plan at **$0.20 per 100k requests**. This means you only pay for what you use. For most use cases, this is a good starting point.
-### Fixed price plans
+#### Fixed price plans
Upstash also offers fixed price plans for when:
@@ -148,10 +152,10 @@ Upstash also offers fixed price plans for when:
These fixed price plans are available via `flyctl redis update `:
* Starter: $10 per month, single region only. Includes 200MB storage, 100 req/sec
-* Standard: $50 per month, per region. Includes 3GB storage, 100 req/sec
+* Standard: $50 per month, $10 per replica region. Includes 3GB storage, 100 req/sec
* Pro 2K: $280 per month, $100 per replica region. Includes 50GB storage, 10k req/sec
Your usage is updated hourly on your monthly Fly.io bill. You can track database usage details in the [Upstash web console](#the-upstash-web-console) as well.
-Check the official [Upstash Pricing](https://upstash.com/pricing) page for more information.
\ No newline at end of file
+Check the official [Upstash Pricing](https://upstash.com/pricing) page for more information.
diff --git a/volumes/overview.html.markerb b/volumes/overview.html.markerb
index 6f67a70d6b..e74043837c 100644
--- a/volumes/overview.html.markerb
+++ b/volumes/overview.html.markerb
@@ -6,9 +6,13 @@ order: 20
redirect_from: /docs/reference/volumes/
---
-Apps can store only ephemeral data on the root file systems of their Fly Machines, because a Machine’s file system gets rebuilt from scratch each time you deploy your app, or when the Machine is restarted.
+
+
+
-Fly Volumes are local persistent storage for [Fly Machines](/docs/machines/). You can access and write to a volume on a Machine just like a regular directory. Use volumes to store your database files, to save your app's state, such as configuration and session or user data, or for any information that needs to persist after deploy or restart.
+The root file systems of a Fly Machine are ephemeral and should only be used for temporary data, application code, and runtime files that can be rebuilt from scratch during deployments or restarts. The performance of these ephemeral disks are heavily limited regardless of the Machine type you choose, with the maximum of 2000 IOPs and 8MiB/s bandwidth.
+
+Fly Volumes are local persistent storage for [Fly Machines](/docs/machines/). You can access and write to a volume on a Machine just like a regular directory. Use volumes to store your database files, to save your app's state, such as configuration and session or user data, or for any information that needs to persist after deploy or restart. Maximum volume performance varies by Machine type and noted in [Volume limits](/docs/volumes/overview#volume-limits)
A Fly Volume is a slice of an NVMe drive on the same physical server as the Machine on which it's mounted and it's tied to that hardware. Fly Volumes are similar to the disk inside your laptop, accessible from a mount point in your file system. Using volumes tied to hardware has both advantages and disadvantages.
@@ -25,7 +29,7 @@ Explore other options for data storage in [Databases & Storage](/docs/database-s
## Volume attachment
-Fly Volumes and Fly Machines are meant to be paired together, but they are not always found in pairs. A Fly Volume can be created without a Fly Machine, or a Machine can be destroyed without destroying its volume. In these cases, the volume that’s left is called an "unattached" volume.
+Fly Volumes and Fly Machines are meant to be paired together, but they are not always found in pairs. A Fly Volume can be created without a Fly Machine, or a Machine can be destroyed without destroying its volume. In these cases, the volume that's left is called an "unattached" volume.
A Fly Machine that does not require a volume will never attach itself to one. A Fly Machine that does require a volume will always be attached to one. When a volume is required according to the app or Machine configuration, any method of creating a new Fly Machine will pick up an unattached volume, create a new volume to attach, or it will fail (in the case of `fly machine` commands or create Machine API calls).
@@ -61,6 +65,23 @@ The default volume size is 1GB when you create a volume with the `fly volumes cr
You can extend a volume's size—either [manually](/docs/volumes/volume-manage/#extend-a-volume) or [automatically](/docs/reference/configuration/#the-mounts-section)—to make it larger, but you can't shrink a volume.
+## Volume limits
+
+The following table outlines the maximum IOPs and bandwidth for Fly Volumes, determined by the Machine type:
+
+| VM Size | Max IOPs | Max Bandwidth |
+|---------|------------------------|----------------------------|
+| shared-cpu-1x | 4000 | 16MiB/s |
+| shared-cpu-2x | 4000 | 16MiB/s |
+| shared-cpu-4x | 8000 | 32MiB/s |
+| shared-cpu-8x | 8000 | 32MiB/s |
+| performance-1x | 12000 | 48MiB/s |
+| performance-2x | 16000 | 64MiB/s |
+| performance-4x | 16000 | 64MiB/s |
+| performance-8x | 32000 | 128MiB/s |
+| performance-16x | 32000 | 128MiB/s |
+
+
## Volume forks
When you [fork a volume](/docs/volumes/volume-manage/#create-a-copy-of-a-volume-fork-a-volume), you create a new volume with an exact copy of the data from the source volume to use for testing, backups, or whatever you like. If you don't specify a region, then the new volume is in the same region, but on a different physical host. The forked volume isn't attached to a Machine until you scale or clone a new Machine in the same region. The new volume and the source volume are independent, and changes to their contents are not synchronized.
@@ -73,6 +94,8 @@ You can also [create a snapshot of a volume](/docs/volumes/snapshots/#create-a-v
You can [restore a volume snapshot](/docs/volumes/snapshots/#restore-a-volume-from-a-snapshot) into a new volume of equal or greater size.
+You can also [disable automatic daily snapshots](/docs/volumes/snapshots/#disable-automatic-daily-snapshots).
+
## When volumes are not available
There are some instances where volumes are not available.
diff --git a/volumes/snapshots.html.markerb b/volumes/snapshots.html.markerb
index 0b04aad533..f33a6bc1b0 100644
--- a/volumes/snapshots.html.markerb
+++ b/volumes/snapshots.html.markerb
@@ -66,14 +66,12 @@ We automatically take daily snapshots of your volume and keep them for 5 days by
Example output:
```out
Snapshots
- ID STATUS SIZE CREATED AT
- vs_yAJylAAN83ZFna0A2yRA3J created 76082175 3 hours ago
- vs_3NlZ9NNmpvoSP4yv3Om1gPm created 76082175 1 day ago
- vs_Y49bw44Z6OocN8j4oXq6ww created 76082175 2 days ago
- vs_gwMAXwwLjOVu1vPQVMN2w3N created 76082175 3 days ago
- vs_Ql8xbllZOYDSmlKB0jxYVpg created 76082175 4 days ago
- vs_plNk4llRGexSL22MOJYRpp created 76082175 5 days ago
- vs_O9jqw997yvOuzyg1NVmmmjxhJ running 0
+ ID STATUS STORED SIZE VOL SIZE CREATED AT RETENTION DAYS
+ vs_A1b2C3d4E5f6G7h8I9j0K1l2 created 2.0 GiB 10 GiB 5 days ago 5
+ vs_M3n4O5p6Q7r8S9t0U1v2W3x4 created 187 MiB 10 GiB 4 days ago 5
+ vs_Y5z6A7b8C9d0E1f2G3h4I5j6 created 45 MiB 10 GiB 3 days ago 5
+ vs_K7l8M9n0O1p2Q3r4S5t6U7v8 created 106 MiB 10 GiB 2 days ago 5
+ vs_O9jqw997yvOuzyg1NVmmmjxh running 0 B 10 GiB
```
The state of the new snapshot will go from `waiting` to `running` to `created`.
@@ -82,7 +80,7 @@ For options, refer to the [`fly volumes snapshots` docs](/docs/flyctl/volumes-sn
## Restore a volume from a snapshot
-To procedure to restore data from a deleted volume is very similar, see [Restore a deleted volume](/docs/volumes/volume-manage/#restore-a-deleted-volume).
+The procedure to restore data from a deleted volume is very similar, see [Restore a deleted volume](/docs/volumes/volume-manage/#restore-a-deleted-volume).
Restore the data from a volume by creating a new volume from a snapshot:
@@ -97,12 +95,12 @@ Restore the data from a volume by creating a new volume from a snapshot:
Example output:
```out
Snapshots
- ID SIZE CREATED AT
- vs_1KRgwpDqZ2ll5tx 17649006 1 day ago
- vs_nymJyYMwXpjxqTzJ 17677766 2 days ago
- vs_R3OPAz5jBqzogF16 17689473 3 days ago
- vs_pZlGZvq3gkAlAcaZ 17655830 4 days ago
- vs_A9k6age3bQov6twj 17631880 5 days ago
+ ID STATUS STORED SIZE VOL SIZE CREATED AT RETENTION DAYS
+ vs_A1b2C3d4E5f6G7h8I9j0K1l2 created 2.0 GiB 10 GiB 5 days ago 5
+ vs_M3n4O5p6Q7r8S9t0U1v2W3x4 created 187 MiB 10 GiB 4 days ago 5
+ vs_Y5z6A7b8C9d0E1f2G3h4I5j6 created 45 MiB 10 GiB 3 days ago 5
+ vs_K7l8M9n0O1p2Q3r4S5t6U7v8 created 106 MiB 10 GiB 2 days ago 5
+ vs_W9x0Y1z2A3b4C5d6E7f8G9h0 created 73 MiB 10 GiB 1 day ago 5
```
1. Restore data from the volume snapshot into a new volume of equal or greater size:
@@ -126,6 +124,58 @@ Restore the data from a volume by creating a new volume from a snapshot:
For options, refer to the [`fly volumes create` docs](/docs/flyctl/volumes-create/) or run `fly volumes create --help`.
+## List snapshots and their stored sizes
+
+Snapshots for each volume are stored incrementally, so only data that has changed since the previously stored snapshot consumes additional storage. The total stored size of the snapshots is used for [Volume Snapshot billing](/docs/about/billing/#volume-snapshot-billing).
+
+1. Run `fly volumes list` and copy the ID of the volume.
+
+1. List the volume's snapshots:
+
+ ```cmd
+ fly volumes snapshots list
+ ```
+
+ Example output:
+ ```out
+ Snapshots
+ ID STATUS STORED SIZE VOL SIZE CREATED AT RETENTION DAYS
+ vs_A1b2C3d4E5f6G7h8I9j0K1l2 created 2.0 GiB 10 GiB 5 days ago 5
+ vs_M3n4O5p6Q7r8S9t0U1v2W3x4 created 187 MiB 10 GiB 4 days ago 5
+ vs_Y5z6A7b8C9d0E1f2G3h4I5j6 created 45 MiB 10 GiB 3 days ago 5
+ vs_K7l8M9n0O1p2Q3r4S5t6U7v8 created 106 MiB 10 GiB 2 days ago 5
+ vs_W9x0Y1z2A3b4C5d6E7f8G9h0 created 73 MiB 10 GiB 1 day ago 5
+
+ Total stored size: 2.5 GiB
+ ```
+
+## Disable automatic daily snapshots
+
+Automatic daily snapshots are enabled by default. To disable automatic snapshots, use the `--scheduled-snapshots=false` flag.
+
+Disable automatic snapshots when you create a volume:
+
+```
+fly volumes create --scheduled-snapshots=false
+```
+
+Disable automatic snapshots for an existing volume:
+
+```
+fly volumes update --scheduled-snapshots=false
+```
+
+Disabling automatic snapshots will not remove any existing snapshots for the volume; snapshots are only removed at the end of their retention periods.
+
+For Fly Launch apps, you can disable automatic snapshots in the `[mounts]` section of your app's `fly.toml` file. Any new Fly Volumes `fly deploy` creates will have automatic snapshots disabled:
+
+```toml
+[mounts]
+source = "myapp_cache"
+destination = "/cache"
+scheduled_snapshots = false
+```
+
## Related topics
- [Fly Volumes overview](/docs/volumes/overview/)
diff --git a/volumes/volume-manage.html.markerb b/volumes/volume-manage.html.markerb
index 28cfce926d..43396d95b8 100644
--- a/volumes/volume-manage.html.markerb
+++ b/volumes/volume-manage.html.markerb
@@ -33,6 +33,8 @@ Use the `--count` option to create more than one volume.
Use the `--snapshot-retention` option to change the number of days we retain snapshots (default is 5 days).
+Use the `--scheduled-snapshots=false` option to disable automatic daily snapshots.
+
For more options, refer to the [`fly volumes create` docs](/docs/flyctl/volumes-create/) or run `fly volumes create --help`.
## Extend a volume
@@ -108,9 +110,11 @@ Create an exact copy of a volume, including its data. By default, we place the n
For options, refer to the [`fly volumes fork` docs](/docs/flyctl/volumes-fork/) or run `fly volumes fork --help`.
+A detailed guide to [volume forking](/docs/blueprints/volume-forking/) is also available.
+
## Add a volume to an unmanaged Machine
-For Machines that aren't managed with Fly Launch (`fly.toml` and `fly deploy`), you can create a volume and attach it when you clone a Machine. You can also [clone a Machine with a volume](/#clone-a-machine-with-a-volume) to get a new Machine with an empty volume.
+For Machines that aren't managed with Fly Launch (`fly.toml` and `fly deploy`), you can create a volume and attach it when you clone a Machine. You can also [clone a Machine with a volume](#clone-a-machine-with-a-volume) to get a new Machine with an empty volume.
1. Create the volume in the same region as your app. For example:
@@ -176,12 +180,16 @@ For options, refer to the [`fly machine clone` docs](/docs/flyctl/machine-clone/
## Destroy a volume
-Warning: When you destroy a volume, you permanently delete all its data.
+Warning: When you destroy a volume, you permanently delete all its data. There is no undo. Make sure you've backed up any data you want to keep.
-1. Run `fly volumes list` and copy the ID of volume to destroy.
+1. List volumes and find the ID for the one you want to destroy:
+
+```cmd
+fly volumes list
+```
-2. Destroy the volume:
+2. Destroy the volume using its ID:
```cmd
fly volumes destroy
@@ -189,6 +197,17 @@ fly volumes destroy
For options, refer to the [`fly volumes destroy` docs](/docs/flyctl/volumes-destroy/) or run `fly volumes destroy --help`.
+**Volumes attached to Machines**
+
+Volumes attached to Machines can’t be destroyed. You can check which Machine (if any) a volume is attached to by running `fly volumes list` First, stop and destroy the Machine, then the unattached volume can be destroyed.
+
+If you’re replacing the Machine, use `fly scale count` or `fly machine clone` to launch a new one. This will create and mount a fresh volume based on your `fly.toml` config.
+
+To stop using volumes entirely, remove any `[mounts]` from `fly.toml`, redeploy to detach them, and then destroy the volumes.
+
+**Note:** Snapshots taken before deletion may still be restorable for a limited time, provided you have the volume ID (default retention is 5 days).
+
+
## Restore a deleted volume
You can restore data from a deleted volume using a volume snapshot. At this time, you can only retrieve a deleted volume's ID for 24 hours after deletion. If you already have the volume ID, then you can still use it to access the volume's snapshots within the snapshot retention period. The default snapshot retention period is 5 days. Learn how to [set or change the snapshot retention period](/docs/volumes/snapshots/#set-or-change-the-snapshot-retention-period).
@@ -239,4 +258,4 @@ For options, refer to the [`fly volumes snapshots` docs](/docs/flyctl/volumes-sn
- [Fly Volumes overview](/docs/volumes/overview/)
- [Manage volume snapshots](/docs/volumes/snapshots/)
- [Add volume storage to a Fly Launch app](/docs/apps/volume-storage/)
-- [Scale an app with volumes](/docs/apps/scale-count/#scale-an-app-with-volumes)
\ No newline at end of file
+- [Scale an app with volumes](/docs/apps/scale-count/#scale-an-app-with-volumes)